//////////////////////////////////////////////////////////////////////////
//
//  E57SimpleImpl.cpp - private implementation header of E57 format reference implementation.
//
//	Copyright (c) 2010 Stan Coleby (scoleby@intelisum.com)
//	All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person or organization
//  obtaining a copy of the software and accompanying documentation covered by
//  this license (the "Software") to use, reproduce, display, distribute,
//  execute, and transmit the Software, and to prepare derivative works of the
//  Software, and to permit third-parties to whom the Software is furnished to
//  do so, all subject to the following:
// 
//  The copyright notices in the Software and this entire statement, including
//  the above license grant, this restriction and the following disclaimer,
//  must be included in all copies of the Software, in whole or in part, and
//  all derivative works of the Software, unless such copies or derivative
//  works are solely in the form of machine-executable object code generated by
//  a source language processor.
// 
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
//  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
//  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
//	The Boost license Vestion 1.0 - August 17th, 2003 is discussed in
//	http://www.boost.org/users/license.html.
//
//  This source code is only intended as a supplement to promote the
//	ASTM E57.04 3D Imaging System File Format standard for interoperability
//	of Lidar Data.  See http://www.libe57.org.
//
//////////////////////////////////////////////////////////////////////////
//
//	New E57SimpleImpl.cpp
//	V1		May 18, 2010	Stan Coleby		scoleby@intelisum.com
//	V6		June 8, 2010	Stan Coleby		scoleby@intelisum.com
//
//////////////////////////////////////////////////////////////////////////

//! @file E57SimpleImpl.cpp

#if defined(WIN32)
#  if defined(_MSC_VER)
#    include <io.h>
#    include <fcntl.h>
#    include <sys\stat.h>
#    include <windows.h>
#  include <boost/uuid/uuid.hpp>
#  include <boost/uuid/uuid_generators.hpp>
#  include <boost/uuid/uuid_io.hpp>
#  elif defined(__GNUC__)
#  define _LARGEFILE64_SOURCE
#  define __LARGE64_FILES
#  include <sys/types.h>
#  include <unistd.h>
#  include <boost/uuid/uuid.hpp>
#  include <boost/uuid/uuid_generators.hpp>
#  include <boost/uuid/uuid_io.hpp>
#    include <fcntl.h>
#    include <sys\stat.h>
#  else
#    error "no supported compiler defined"
#  endif
#elif defined(LINUX)
#  define _LARGEFILE64_SOURCE
#  define __LARGE64_FILES
#  include <sys/types.h>
#  include <unistd.h>
#  include <boost/uuid/uuid.hpp>
#  include <boost/uuid/uuid_generators.hpp>
#  include <boost/uuid/uuid_io.hpp>
#else
#  error "no supported OS platform defined"
#endif

#include <sstream>
#include "E57SimpleImpl.h"
#include "time_conversion.h"

using namespace e57;
using namespace std;
using namespace boost;

namespace e57 {
char *	GetNewGuid(void);
double	GetGPSTime(void);

double	GetGPSDateTimeFromUTC(
	int utc_year,		//!< The year 1900-9999
	int utc_month,		//!< The month 1-12
	int utc_day,		//!< The day 1-31
	int utc_hour,		//!< The hour 0-23
	int utc_minute,		//!< The minute 0-59
	float utc_seconds	//!< The seconds 0.0 - 59.999
	);

void	GetUTCFromGPSDateTime(
    double gpsTime,		//!< GPS Date Time
	int &utc_year,		//!< The year 1900-9999
	int &utc_month,		//!< The month 1-12
	int &utc_day,		//!< The day 1-31
	int &utc_hour,		//!< The hour 0-23
	int &utc_minute,		//!< The minute 0-59
	float &utc_seconds	//!< The seconds 0.0 - 59.999
	);
#if defined(WIN32)
double	GetGPSDateTimeFromSystemTime(
	SYSTEMTIME	sysTim		//!< Windows System Time
	);
void	GetSystemTimeFromGPSDateTime(
	double		gpsTime,	//!< GPS Date Time
	SYSTEMTIME	&sysTim		//!< Windows System Time
	);
#endif
};
////////////////////////////////////////////////////////////////////
//
//	e57::GetGPSTime
//
double e57::GetGPSTime(void)
{
#ifdef _C_TIMECONV_H_

unsigned short     utc_year;     //!< Universal Time Coordinated    [year]
unsigned char      utc_month;    //!< Universal Time Coordinated    [1-12 months] 
unsigned char      utc_day;      //!< Universal Time Coordinated    [1-31 days]
unsigned char      utc_hour;     //!< Universal Time Coordinated    [hours]
unsigned char      utc_minute;   //!< Universal Time Coordinated    [minutes]
float              utc_seconds;  //!< Universal Time Coordinated    [s]
unsigned char      utc_offset;   //!< Integer seconds that GPS is ahead of UTC time, always positive             [s], obtained from a look up table
double             julian_date;  //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
unsigned short     gps_week;     //!< GPS week (0-1024+)            [week]
double             gps_tow;	  //!< GPS time of week (0-604800.0) [s]

BOOL ret = TIMECONV_GetSystemTime(&utc_year, &utc_month, &utc_day, &utc_hour, &utc_minute, &utc_seconds,
	&utc_offset, &julian_date, &gps_week, &gps_tow);

double gpsTime = (gps_week * 604800.) + gps_tow;

#elif defined(WIN32)
SYSTEMTIME		currentSystemTime;
GetSystemTime(&currentSystemTime);	//current UTC Time
double gpsTime = e57::GetGPSDateTimeFromSystemTime(currentSystemTime);
#endif

return gpsTime;
};
#if defined(WIN32)
////////////////////////////////////////////////////////////////////
//
//	e57::GetGPSDateTimeFromSystemTime
//
double	e57::GetGPSDateTimeFromSystemTime(
	SYSTEMTIME	sysTim		//!< Windows System Time
	)
{
#ifdef _C_TIMECONV_H_
	int utc_year = sysTim.wYear;		//!< The year 1900-9999
	int utc_month = sysTim.wMonth;		//!< The month 0-11
	int utc_day = sysTim.wDay;			//!< The day 1-31
	int utc_hour = sysTim.wHour;		//!< The hour 0-23
	int utc_minute = sysTim.wMinute;	//!< The minute 0-59
	float utc_seconds = sysTim.wSecond;	//!< The seconds 0.0 - 59.999
	utc_seconds += sysTim.wMilliseconds/1000;

	double gpsTime = e57::GetGPSDateTimeFromUTC(
		utc_year, utc_month, utc_day, utc_hour, utc_minute, utc_seconds);
#else
	FILETIME		currentFileTime;
	SystemTimeToFileTime(&sysTim,&currentFileTime);

	ULARGE_INTEGER	currentTime;
	currentTime.LowPart = currentFileTime.dwLowDateTime;
	currentTime.HighPart = currentFileTime.dwHighDateTime;

	SYSTEMTIME		gpsSystemTime = {1980,1,0,6,0,0,0,0};	//GPS started in Jan. 6, 1980
	FILETIME		gpsFileTime;
	SystemTimeToFileTime(&gpsSystemTime,&gpsFileTime);

	ULARGE_INTEGER	gpsStartTime;
	gpsStartTime.LowPart = gpsFileTime.dwLowDateTime;
	gpsStartTime.HighPart = gpsFileTime.dwHighDateTime;

	double gpsTime = (double) (currentTime.QuadPart - gpsStartTime.QuadPart);	//number of 100 nanosecond;
	gpsTime /= 10000000.;	//number of seconds
	gpsTime += 15.;			//Add utc offset leap seconds
#endif
	return gpsTime;
};
////////////////////////////////////////////////////////////////////
//
//	e57::GetSystemTimeFromGPSDateTime
//
void	e57::GetSystemTimeFromGPSDateTime(
	double		gpsTime,	//!< GPS Date Time
	SYSTEMTIME	&sysTim		//!< Windows System Time
	)
{
#ifdef _C_TIMECONV_H_
	int utc_year;		//!< The year 1900-9999
	int utc_month;		//!< The month 0-11
	int utc_day;		//!< The day 1-31
	int utc_hour;		//!< The hour 0-23
	int utc_minute;		//!< The minute 0-59
	float utc_seconds;	//!< The seconds 0.0 - 59.999

	e57::GetUTCFromGPSDateTime(gpsTime, utc_year, utc_month,
		utc_day, utc_hour, utc_minute, utc_seconds);

	double julian_date = 0;
	unsigned char day_of_week = 0;

	TIMECONV_GetJulianDateFromUTCTime( utc_year, utc_month, utc_day,
		utc_hour, utc_minute, utc_seconds, &julian_date );

	TIMECONV_GetDayOfWeekFromJulianDate( julian_date, &day_of_week );

	sysTim.wDayOfWeek = day_of_week;
	sysTim.wYear = utc_year;
	sysTim.wMonth = utc_month;
	sysTim.wDay = utc_day;
	sysTim.wHour = utc_hour;
	sysTim.wMinute = utc_minute;
	sysTim.wSecond = (WORD)(floor(utc_seconds));
	sysTim.wMilliseconds = (WORD)((utc_seconds - sysTim.wSecond)*1000);

#else
	gpsTime -= 15.;			//Sub utc offset leap seconds
	gpsTime *= 10000000.;	//convert to 100 nanoseconds;

	SYSTEMTIME		gpsSystemTime = {1980,1,0,6,0,0,0,0};	//GPS started in Jan. 6, 1980
	FILETIME		gpsFileTime;
	SystemTimeToFileTime(&gpsSystemTime,&gpsFileTime);

	ULARGE_INTEGER	gpsStartTime;
	gpsStartTime.LowPart = gpsFileTime.dwLowDateTime;
	gpsStartTime.HighPart = gpsFileTime.dwHighDateTime;

	ULARGE_INTEGER	currentTime;
	currentTime.QuadPart = ((ULONGLONG)gpsTime) + gpsStartTime.QuadPart;

	FILETIME		currentFileTime;
	currentFileTime.dwLowDateTime = currentTime.LowPart;
	currentFileTime.dwHighDateTime = currentTime.HighPart;
	FileTimeToSystemTime(&currentFileTime,&sysTim);
#endif
};
#endif
////////////////////////////////////////////////////////////////////
//
//	e57::GetGPSDateTimeFromUTC
//
double	e57::GetGPSDateTimeFromUTC(
	int utc_year,		//!< The year 1900-9999
	int utc_month,		//!< The month 0-11
	int utc_day,		//!< The day 1-31
	int utc_hour,		//!< The hour 0-23
	int utc_minute,		//!< The minute 0-59
	float utc_seconds	//!< The seconds 0.0 - 59.999
	)
{
#ifdef _C_TIMECONV_H_
 	double             julian_date;  //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
	unsigned char      utc_offset;   //!< Integer seconds that GPS is ahead of UTC time, always positive  
	unsigned short     gps_week;     //!< GPS week (0-1024+)            [week]
	double             gps_tow;	  //!< GPS time of week (0-604800.0) [s]

	BOOL result = TIMECONV_GetJulianDateFromUTCTime(
	    utc_year,
		utc_month,
		utc_day,
		utc_hour,
		utc_minute,
		utc_seconds,
		&julian_date );
 
	result = TIMECONV_DetermineUTCOffset(
		julian_date,
		&utc_offset );
 
	result = TIMECONV_GetGPSTimeFromJulianDate(
		julian_date,
		utc_offset,
		&gps_week,
		&gps_tow );

	double gpsTime = (gps_week * 604800.) + gps_tow;

#elif defined(WIN32)
	SYSTEMTIME	sysTim;
	sysTim.wDayOfWeek = day_of_week;
	sysTim.wYear = utc_year;
	sysTim.wMonth = utc_month;
	sysTim.wDay = utc_day;
	sysTim.wHour = utc_hour;
	sysTim.wMinute = utc_minute;
	sysTim.wSecond = (WORD)(floor(utc_seconds));
	sysTim.wMilliseconds = (WORD)((utc_seconds - t.wSecond)*1000);

	double gpsTime = e57::GetGPSDateTimeFromSystemTime(sysTim);
#endif
	return gpsTime;
};
////////////////////////////////////////////////////////////////////
//
//	e57::GetUTCFromGPSDateTime
//
void	e57::GetUTCFromGPSDateTime(
    double gpsTime,		//!< GPS Date Time
	int &utc_Year,		//!< The year 1900-9999
	int &utc_Month,		//!< The month 0-11
	int &utc_Day,		//!< The day 1-31
	int &utc_Hour,		//!< The hour 0-23
	int &utc_Minute,		//!< The minute 0-59
	float &utc_seconds	//!< The seconds 0.0 - 59.999
	)
{
#ifdef _C_TIMECONV_H_
	unsigned short     utc_year;     //!< Universal Time Coordinated    [year]
	unsigned char      utc_month;    //!< Universal Time Coordinated    [1-12 months] 
	unsigned char      utc_day;      //!< Universal Time Coordinated    [1-31 days]
	unsigned char      utc_hour;     //!< Universal Time Coordinated    [hours]
	unsigned char      utc_minute;   //!< Universal Time Coordinated    [minutes]
 	unsigned short     gps_week;     //!< GPS week (0-1024+)            [week]
	double             gps_tow;	  //!< GPS time of week (0-604800.0) [s]

	gps_week = ((int)floor(gpsTime))/604800;
	gps_tow = gpsTime - gps_week * 604800.;

	BOOL result = TIMECONV_GetUTCTimeFromGPSTime(
		gps_week,
		gps_tow,
		&utc_year,
		&utc_month,
		&utc_day,
		&utc_hour,
		&utc_minute,
		&utc_seconds);

	utc_Year = utc_year;
	utc_Month = utc_month;
	utc_Day = utc_day;
	utc_Hour = utc_hour;
	utc_Minute = utc_minute;

#elif defined(WIN32)
	SYSTEMTIME	sysTim;
	e57::GetSystemTimeFromGPSDateTime(gpsTime,sysTim);

	utc_year = sysTim.wYear;		//!< The year 1900-9999
	utc_month = sysTim.wMonth;		//!< The month 0-11
	utc_day = sysTim.wDay;			//!< The day 1-31
	utc_hour = sysTim.wHour;		//!< The hour 0-23
	utc_minute = sysTim.wMinute;	//!< The minute 0-59
	utc_seconds = sysTim.wSecond;	//!< The seconds 0.0 - 59.999
	utc_seconds += sysTim.wMilliseconds/1000;
#endif
	return;
};
////////////////////////////////////////////////////////////////////
//
//	e57::GetNewGuid
//
char * e57::GetNewGuid(void)
{
	static char	fileGuid[64];
#if defined(_MSC_VER)
	GUID		guid;
	CoCreateGuid((GUID*)&guid);
	OLECHAR wbuffer[64];
	StringFromGUID2(guid,&wbuffer[0],64);
	size_t	converted = 0;
	wcstombs_s(&converted, fileGuid,wbuffer,64);

#else
	boost::uuids::random_generator gen;
	boost::uuids::uuid u = gen();
	std::stringstream s;
	s << u;
	std::string c = s.str();

	fileGuid[0] = '{';
	memcpy(&fileGuid[1],&c[0],36);
	fileGuid[37] = '}';
	fileGuid[38] = 0;
#endif
	return fileGuid;
};
////////////////////////////////////////////////////////////////////
//
//	e57::ReaderImpl
//
	ReaderImpl::ReaderImpl(
		const ustring & filePath)
	: imf_(filePath,"r")
	, root_(imf_.root())
	, data3D_(root_.get("/data3D"))
	, images2D_(root_.get("/images2D"))
{
};

	ReaderImpl::~ReaderImpl(void)
{
	if(IsOpen())
		Close();
};


//! This function returns true if the file is open
bool	ReaderImpl :: IsOpen(void)
{
	if( imf_.isOpen())
		return true;
	return false;
};

//! This function closes the file
bool	ReaderImpl :: Close(void)
{
	if(IsOpen())
	{
		imf_.close();
		return true;
	}
	return false;
};

////////////////////////////////////////////////////////////////////
//
//	File information
//
//! This function returns the file header information
bool	ReaderImpl :: GetE57Root(
	E57Root & fileHeader)	//!< This is the main header information
{
	if(IsOpen())
	{
		fileHeader.Reset();

		fileHeader.formatName = StringNode(root_.get("formatName")).value();
		fileHeader.versionMajor = (int32_t) IntegerNode(root_.get("versionMajor")).value();
		fileHeader.versionMinor = (int32_t) IntegerNode(root_.get("versionMinor")).value();
		fileHeader.guid = StringNode(root_.get("guid")).value();
		if(root_.isDefined("e57LibraryVersion"))
			fileHeader.e57LibraryVersion = StringNode(root_.get("e57LibraryVersion")).value();

		if(root_.isDefined("coordinateMetadata"))
			fileHeader.coordinateMetadata = StringNode(root_.get("coordinateMetadata")).value();

		if(root_.isDefined("creationDateTime"))
		{
			StructureNode creationDateTime(root_.get("creationDateTime"));
			fileHeader.creationDateTime.dateTimeValue =
				FloatNode(creationDateTime.get("dateTimeValue")).value();
			fileHeader.creationDateTime.isAtomicClockReferenced =
				(int32_t) IntegerNode(creationDateTime.get("isAtomicClockReferenced")).value();
		}

		fileHeader.data3DSize = (int32_t) data3D_.childCount();
		fileHeader.images2DSize = (int32_t) images2D_.childCount();

		return true;
	}
	return false;
};

////////////////////////////////////////////////////////////////////
//
//	Camera Image picture data
//
//! This function returns the total number of Picture Blocks
int32_t	ReaderImpl :: GetImage2DCount( void)
{
	return (int32_t) images2D_.childCount();
};

//! This function returns the Image2Ds header and positions the cursor
bool	ReaderImpl :: ReadImage2D( 
	int32_t		imageIndex,		//!< This in the index into the Image2Ds vector
	Image2D &	image2DHeader	//!< pointer to the Image2D structure to receive the picture information
	)							//!< /return Returns true if sucessful
{
	if(IsOpen())
	{
		if( (imageIndex < 0) || (imageIndex >= images2D_.childCount()))
			return false;

		image2DHeader.Reset();

		StructureNode image(images2D_.get(imageIndex));

		image2DHeader.guid = StringNode(image.get("guid")).value();

		if(image.isDefined("name"))
			image2DHeader.name = StringNode(image.get("name")).value();

		if(image.isDefined("description"))
			image2DHeader.description = StringNode(image.get("description")).value();

		if(image.isDefined("sensorVendor"))
			image2DHeader.sensorVendor = StringNode(image.get("sensorVendor")).value();
		if(image.isDefined("sensorModel"))
			image2DHeader.sensorModel = StringNode(image.get("sensorModel")).value();
		if(image.isDefined("sensorSerialNumber"))
			image2DHeader.sensorSerialNumber = StringNode(image.get("sensorSerialNumber")).value();

		if(image.isDefined("associatedData3DGuid"))
			image2DHeader.associatedData3DGuid = StringNode(image.get("associatedData3DGuid")).value();

		if(image.isDefined("acquisitionDateTime"))
		{
			StructureNode acquisitionDateTime(image.get("acquisitionDateTime"));
			image2DHeader.acquisitionDateTime.dateTimeValue = 
				FloatNode(acquisitionDateTime.get("dateTimeValue")).value();
			image2DHeader.acquisitionDateTime.isAtomicClockReferenced = (int32_t) 
				IntegerNode(acquisitionDateTime.get("isAtomicClockReferenced")).value();
		}
// Get pose structure for scan.

		if(image.isDefined("pose"))
		{
			StructureNode pose(image.get("pose"));
			if(pose.isDefined("rotation")){
				StructureNode rotation(pose.get("rotation"));
				image2DHeader.pose.rotation.w = FloatNode(rotation.get("w")).value();
				image2DHeader.pose.rotation.x = FloatNode(rotation.get("x")).value();
				image2DHeader.pose.rotation.y = FloatNode(rotation.get("y")).value();
				image2DHeader.pose.rotation.z = FloatNode(rotation.get("z")).value();
			}
			if(pose.isDefined("translation")){
 				StructureNode translation(pose.get("translation")); 
				image2DHeader.pose.translation.x = FloatNode(translation.get("x")).value();
				image2DHeader.pose.translation.y = FloatNode(translation.get("y")).value();
				image2DHeader.pose.translation.z = FloatNode(translation.get("z")).value();
			}
		}

		if(image.isDefined("visualReferenceRepresentation"))
		{
			StructureNode visualReferenceRepresentation(image.get("visualReferenceRepresentation"));

			if(visualReferenceRepresentation.isDefined("jpegImage"))
				image2DHeader.visualReferenceRepresentation.jpegImageSize = 
					BlobNode(visualReferenceRepresentation.get("jpegImage")).byteCount();
			if(visualReferenceRepresentation.isDefined("pngImage"))
				image2DHeader.visualReferenceRepresentation.pngImageSize = 
					BlobNode(visualReferenceRepresentation.get("pngImage")).byteCount();
			if(visualReferenceRepresentation.isDefined("imageMask"))
				image2DHeader.visualReferenceRepresentation.imageMaskSize = 
					BlobNode(visualReferenceRepresentation.get("imageMask")).byteCount();

			image2DHeader.visualReferenceRepresentation.imageHeight = (int32_t)
				IntegerNode(visualReferenceRepresentation.get("imageHeight")).value();
			image2DHeader.visualReferenceRepresentation.imageWidth = (int32_t)
				IntegerNode(visualReferenceRepresentation.get("imageWidth")).value();
		}

		if(image.isDefined("pinholeRepresentation"))
		{
			StructureNode pinholeRepresentation(image.get("pinholeRepresentation"));

			if(pinholeRepresentation.isDefined("jpegImage"))
				image2DHeader.pinholeRepresentation.jpegImageSize = 
					BlobNode(pinholeRepresentation.get("jpegImage")).byteCount();
			if(pinholeRepresentation.isDefined("pngImage"))
				image2DHeader.pinholeRepresentation.pngImageSize = 
					BlobNode(pinholeRepresentation.get("pngImage")).byteCount();
			if(pinholeRepresentation.isDefined("imageMask"))
				image2DHeader.pinholeRepresentation.imageMaskSize = 
					BlobNode(pinholeRepresentation.get("imageMask")).byteCount();

			image2DHeader.pinholeRepresentation.focalLength = 
				FloatNode(pinholeRepresentation.get("focalLength")).value();
			image2DHeader.pinholeRepresentation.imageHeight = (int32_t)
				IntegerNode(pinholeRepresentation.get("imageHeight")).value();
			image2DHeader.pinholeRepresentation.imageWidth = (int32_t)
				IntegerNode(pinholeRepresentation.get("imageWidth")).value();

			image2DHeader.pinholeRepresentation.pixelHeight = 
				FloatNode(pinholeRepresentation.get("pixelHeight")).value();
			image2DHeader.pinholeRepresentation.pixelWidth = 
				FloatNode(pinholeRepresentation.get("pixelWidth")).value();
			image2DHeader.pinholeRepresentation.principalPointX = 
				FloatNode(pinholeRepresentation.get("principalPointX")).value();
			image2DHeader.pinholeRepresentation.principalPointY = 
				FloatNode(pinholeRepresentation.get("principalPointY")).value();
		}
		else if(image.isDefined("sphericalRepresentation"))
		{
			StructureNode sphericalRepresentation(image.get("sphericalRepresentation"));

			if(sphericalRepresentation.isDefined("jpegImage"))
				image2DHeader.sphericalRepresentation.jpegImageSize = 
					BlobNode(sphericalRepresentation.get("jpegImage")).byteCount();
			if(sphericalRepresentation.isDefined("pngImage"))
				image2DHeader.sphericalRepresentation.pngImageSize = 
					BlobNode(sphericalRepresentation.get("pngImage")).byteCount();
			if(sphericalRepresentation.isDefined("imageMask"))
				image2DHeader.sphericalRepresentation.imageMaskSize = 
					BlobNode(sphericalRepresentation.get("imageMask")).byteCount();

			image2DHeader.sphericalRepresentation.imageHeight = (int32_t)
				IntegerNode(sphericalRepresentation.get("imageHeight")).value();
			image2DHeader.sphericalRepresentation.imageWidth = (int32_t)
				IntegerNode(sphericalRepresentation.get("imageWidth")).value();

			image2DHeader.sphericalRepresentation.pixelHeight = 
				FloatNode(sphericalRepresentation.get("pixelHeight")).value();
			image2DHeader.sphericalRepresentation.pixelWidth = 
				FloatNode(sphericalRepresentation.get("pixelWidth")).value();
		}
		else if(image.isDefined("cylindricalRepresentation"))
		{
			StructureNode cylindricalRepresentation(image.get("cylindricalRepresentation"));

			if(cylindricalRepresentation.isDefined("jpegImage"))
				image2DHeader.cylindricalRepresentation.jpegImageSize = 
					BlobNode(cylindricalRepresentation.get("jpegImage")).byteCount();
			if(cylindricalRepresentation.isDefined("pngImage"))
				image2DHeader.cylindricalRepresentation.pngImageSize = 
					BlobNode(cylindricalRepresentation.get("pngImage")).byteCount();
			if(cylindricalRepresentation.isDefined("imageMask"))
				image2DHeader.cylindricalRepresentation.imageMaskSize = 
					BlobNode(cylindricalRepresentation.get("imageMask")).byteCount();

			image2DHeader.cylindricalRepresentation.imageHeight = (int32_t)
				IntegerNode(cylindricalRepresentation.get("imageHeight")).value();
			image2DHeader.cylindricalRepresentation.imageWidth = (int32_t)
				IntegerNode(cylindricalRepresentation.get("imageWidth")).value();

			image2DHeader.cylindricalRepresentation.pixelHeight = 
				FloatNode(cylindricalRepresentation.get("pixelHeight")).value();
			image2DHeader.cylindricalRepresentation.pixelWidth = 
				FloatNode(cylindricalRepresentation.get("pixelWidth")).value();
			image2DHeader.cylindricalRepresentation.principalPointY = 
				FloatNode(cylindricalRepresentation.get("principalPointY")).value();
			image2DHeader.cylindricalRepresentation.radius = 
				FloatNode(cylindricalRepresentation.get("radius")).value();
		}
		return true;
	}
	return false;
};
//! This function reads one of the image blobs
int64_t ReaderImpl :: ReadImage2DNode(
	e57::StructureNode		image,			//!< 1 of 3 projects or the visual
	e57::Image2DType		imageType,		//!< identifies the image format desired.
	void *					pBuffer,		//!< pointer the buffer
	int64_t					start,			//!< position in the block to start reading
	int64_t					count			//!< size of desired chuck or buffer size
	)										//!< /return Returns the number of bytes transferred.
{
	int64_t transferred = 0;
	switch(imageType)
	{
	case	E57_JPEG_IMAGE:
		{
			if(image.isDefined("jpegImage"))
			{
				BlobNode jpegImage(image.get("jpegImage"));
				jpegImage.read((uint8_t*) pBuffer, start, (size_t) count);
				transferred = count;
			}
			break;
		}
	case	E57_PNG_IMAGE:
		{
			if(	image.isDefined("pngImage"))
			{
				BlobNode pngImage(image.get("pngImage"));
				pngImage.read((uint8_t*) pBuffer, start, (size_t) count);
				transferred = count;
			}
			break;
		}
	case	E57_PNG_IMAGE_MASK:
		{
			if(	image.isDefined("imageMask"))
			{
				BlobNode imageMask(image.get("imageMask"));
				imageMask.read((uint8_t*) pBuffer, start, (size_t) count);
				transferred = count;
			}
			break;
		}
	};
	return transferred;
};
//! This function reads one of the image blobs
bool ReaderImpl :: GetImage2DNodeSizes(
	e57::StructureNode		image,			//!< 1 of 3 projects or the visual
	e57::Image2DType &		imageType,		//!< identifies the image format desired.
	int64_t &				imageWidth,		//!< The image width (in pixels).
	int64_t &				imageHeight,	//!< The image height (in pixels).
	int64_t &				imageSize,		//!< This is the total number of bytes for the image blob.
	e57::Image2DType &		imageMaskType	//!< This is E57_PNG_IMAGE_MASK if "imageMask" is defined in the projection
	)										//!< /return Returns the number of bytes transferred.
{
	imageWidth = 0;
	imageHeight = 0;
	imageSize = 0;
	imageType = E57_NO_IMAGE;
	imageMaskType = E57_NO_IMAGE;

	if(image.isDefined("imageWidth"))
		imageWidth = IntegerNode(image.get("imageWidth")).value();
	else return false;
	
	if(image.isDefined("imageHeight"))
		imageHeight = IntegerNode(image.get("imageHeight")).value();
	else return false;

	if(image.isDefined("jpegImage"))
	{
		imageSize = BlobNode(image.get("jpegImage")).byteCount();
		imageType = E57_JPEG_IMAGE;
	}
	else if( image.isDefined("pngImage"))
	{
		imageSize = BlobNode(image.get("pngImage")).byteCount();
		imageType = E57_PNG_IMAGE;
	}

	if(	image.isDefined("imageMask"))
	{
		if(imageType == E57_NO_IMAGE)
		{
			imageSize = BlobNode(image.get("imageMask")).byteCount();
			imageType = E57_PNG_IMAGE_MASK;
		}
		imageMaskType = E57_PNG_IMAGE_MASK;
	}
	return true;
};

// This function returns the image sizes
bool		ReaderImpl :: GetImage2DSizes(
	int32_t					imageIndex,		//!< This in the index into the image2D vector
	e57::Image2DProjection &imageProjection,//!< identifies the projection desired.
	e57::Image2DType &		imageType,		//!< identifies the image format desired.
	int64_t &				imageWidth,		//!< The image width (in pixels).
	int64_t &				imageHeight,	//!< The image height (in pixels).
	int64_t &				imageSize,		//!< This is the total number of bytes for the image blob.
	e57::Image2DType &		imageMaskType,	//!< This is E57_PNG_IMAGE_MASK if "imageMask" is defined in the projection
	e57::Image2DType &		imageVisualType	//!< This is image type of the VisualReferenceRepresentation if given.
	)
{
	if( (imageIndex < 0) || (imageIndex >= images2D_.childCount()))
		return 0;

	imageProjection = E57_NO_PROJECTION;
	imageType = E57_NO_IMAGE;
	imageMaskType = E57_NO_IMAGE;
	imageVisualType = E57_NO_IMAGE;

	bool ret = false;
	StructureNode image(images2D_.get(imageIndex));

	if(image.isDefined("visualReferenceRepresentation"))
	{
		imageProjection = E57_VISUAL;
		StructureNode visualReferenceRepresentation(image.get("visualReferenceRepresentation"));
		ret = GetImage2DNodeSizes(visualReferenceRepresentation, imageType, imageWidth, imageHeight, imageSize, imageMaskType);
		imageVisualType = imageType;
	}

	if(image.isDefined("pinholeRepresentation"))
	{
		imageProjection = E57_PINHOLE;
		StructureNode pinholeRepresentation(image.get("pinholeRepresentation"));
		ret = GetImage2DNodeSizes(pinholeRepresentation, imageType, imageWidth, imageHeight, imageSize, imageMaskType);
	}
	else if(image.isDefined("sphericalRepresentation"))
	{
		imageProjection = E57_SPHERICAL;
		StructureNode sphericalRepresentation(image.get("sphericalRepresentation"));
		ret = GetImage2DNodeSizes(sphericalRepresentation, imageType, imageWidth, imageHeight, imageSize, imageMaskType);
	}
	else if(image.isDefined("cylindricalRepresentation"))
	{
		imageProjection = E57_CYLINDRICAL;
		StructureNode cylindricalRepresentation(image.get("cylindricalRepresentation"));
		ret = GetImage2DNodeSizes(cylindricalRepresentation, imageType, imageWidth, imageHeight, imageSize, imageMaskType);
	};
	return ret;
};
//! This function reads the block
int64_t	ReaderImpl :: ReadImage2DData(
	int32_t					imageIndex,		//!< picture block index
	e57::Image2DProjection	imageProjection,//!< identifies the projection desired.
	e57::Image2DType		imageType,		//!< identifies the image format desired.
	void *					pBuffer,		//!< pointer the buffer
	int64_t					start,			//!< position in the block to start reading
	int64_t					count			//!< size of desired chuck or buffer size
	)										//!< /return Returns the number of bytes transferred.
{
	if( (imageIndex < 0) || (imageIndex >= images2D_.childCount()))
		return 0;

	int64_t transferred = 0;
	StructureNode image(images2D_.get(imageIndex));

	switch(imageProjection)
	{
	case	E57_VISUAL:
		if(image.isDefined("visualReferenceRepresentation"))
		{
			StructureNode visualReferenceRepresentation(image.get("visualReferenceRepresentation"));
			transferred = ReadImage2DNode(visualReferenceRepresentation, imageType, pBuffer, start, count);
		}
		break;

	case	E57_PINHOLE:
		if(image.isDefined("pinholeRepresentation"))
		{
			StructureNode pinholeRepresentation(image.get("pinholeRepresentation"));
			transferred = ReadImage2DNode(pinholeRepresentation, imageType, pBuffer, start, count);
		}
		break;

	case	E57_SPHERICAL:
		if(image.isDefined("sphericalRepresentation"))
		{
			StructureNode sphericalRepresentation(image.get("sphericalRepresentation"));
			transferred = ReadImage2DNode(sphericalRepresentation, imageType, pBuffer, start, count);
		}
		break;

	case	E57_CYLINDRICAL:
		if(image.isDefined("cylindricalRepresentation"))
		{
			StructureNode cylindricalRepresentation(image.get("cylindricalRepresentation"));
			transferred = ReadImage2DNode(cylindricalRepresentation, imageType, pBuffer, start, count);
		}
		break;
	};
	return transferred;
};

////////////////////////////////////////////////////////////////////
//
//	Scanner Image 3d data
//
//! This function returns the total number of Image Blocks
int32_t	ReaderImpl :: GetData3DCount( void)
{
	return (int32_t) data3D_.childCount();
};

//! This function returns the file raw E57Root Structure Node
StructureNode	ReaderImpl :: GetRawE57Root(void)
{
	return root_;
};	//!< /return Returns the E57Root StructureNode

//! This function returns the raw Data3D Vector Node
VectorNode		ReaderImpl :: GetRawData3D(void)
{
	return data3D_;
};//!< /return Returns the raw Data3D VectorNode

//! This function returns the raw Images2D Vector Node
VectorNode		ReaderImpl :: GetRawImages2D(void)
{
	return images2D_;
};	//!< /return Returns the raw Image2D VectorNode

//! This function returns the Data3D header and positions the cursor
bool	ReaderImpl :: ReadData3D( 
	int32_t		dataIndex,	//!< This in the index into the images3D vector
	Data3D &	data3DHeader //!< pointer to the Data3D structure to receive the image information
	)	//!< /return Returns true if sucessful
{
	if(IsOpen())
	{
		if( (dataIndex < 0) || (dataIndex >= data3D_.childCount()))
			return false;

		data3DHeader.Reset();

		StructureNode scan(data3D_.get(dataIndex));
		CompressedVectorNode points(scan.get("points"));

		data3DHeader.pointsSize = points.childCount();
		StructureNode proto(points.prototype());

		data3DHeader.guid = StringNode(scan.get("guid")).value();

		if(scan.isDefined("name"))
			data3DHeader.name = StringNode(scan.get("name")).value();
		if(scan.isDefined("description"))
			data3DHeader.description = StringNode(scan.get("description")).value();

		if(scan.isDefined("originalGuids"))
		{
			VectorNode originalGuids(scan.get("originalGuids"));
			if(originalGuids.childCount() > 0)
			{
				data3DHeader.originalGuids.clear();
				int i;
				for( i = 0; i < originalGuids.childCount(); i++)
				{
					e57::ustring str = StringNode(originalGuids.get(i)).value();
					data3DHeader.originalGuids.push_back(str);
				}
			}
		}

// Get various sensor and version strings to scan.

		if(scan.isDefined("sensorVendor"))
			data3DHeader.sensorVendor = StringNode(scan.get("sensorVendor")).value();
		if(scan.isDefined("sensorModel"))
			data3DHeader.sensorModel = StringNode(scan.get("sensorModel")).value();
		if(scan.isDefined("sensorSerialNumber"))
			data3DHeader.sensorSerialNumber = StringNode(scan.get("sensorSerialNumber")).value();
		if(scan.isDefined("sensorHardwareVersion"))
			data3DHeader.sensorHardwareVersion = StringNode(scan.get("sensorHardwareVersion")).value();
		if(scan.isDefined("sensorSoftwareVersion"))
			data3DHeader.sensorSoftwareVersion = StringNode(scan.get("sensorSoftwareVersion")).value();
		if(scan.isDefined("sensorFirmwareVersion"))
			data3DHeader.sensorFirmwareVersion = StringNode(scan.get("sensorFirmwareVersion")).value();

// Get temp/humidity to scan.

		if(scan.isDefined("temperature"))
			data3DHeader.temperature = (float) FloatNode(scan.get("temperature")).value();
		if(scan.isDefined("relativeHumidity"))
			data3DHeader.relativeHumidity = (float) FloatNode(scan.get("relativeHumidity")).value();
		if(scan.isDefined("atmosphericPressure"))
			data3DHeader.atmosphericPressure = (float) FloatNode(scan.get("atmosphericPressure")).value();

		if(scan.isDefined("indexBounds"))
		{
			StructureNode ibox(scan.get("indexBounds"));
			if(ibox.isDefined("rowMaximum"))
			{
				data3DHeader.indexBounds.rowMinimum = IntegerNode(ibox.get("rowMinimum")).value();
				data3DHeader.indexBounds.rowMaximum = IntegerNode(ibox.get("rowMaximum")).value();
			}
			if(ibox.isDefined("columnMaximum"))
			{
				data3DHeader.indexBounds.columnMinimum = IntegerNode(ibox.get("columnMinimum")).value();
				data3DHeader.indexBounds.columnMaximum = IntegerNode(ibox.get("columnMaximum")).value();
			}
			if(ibox.isDefined("returnMaximum"))
			{
				data3DHeader.indexBounds.returnMinimum = IntegerNode(ibox.get("returnMinimum")).value();
				data3DHeader.indexBounds.returnMaximum = IntegerNode(ibox.get("returnMaximum")).value();
			}
		}

		if(scan.isDefined("pointGroupingSchemes"))
		{
			StructureNode pointGroupingSchemes(scan.get("pointGroupingSchemes"));
			if(pointGroupingSchemes.isDefined("groupingByLine"))
			{
				StructureNode groupingByLine(pointGroupingSchemes.get("groupingByLine"));

				data3DHeader.pointGroupingSchemes.groupingByLine.idElementName =
					StringNode(groupingByLine.get("idElementName")).value();

				CompressedVectorNode groups(groupingByLine.get("groups"));
				data3DHeader.pointGroupingSchemes.groupingByLine.groupsSize = groups.childCount();

				StructureNode lineGroupRecord(groups.prototype());
				if(lineGroupRecord.isDefined("pointCount"))
					data3DHeader.pointGroupingSchemes.groupingByLine.pointCountSize =
						IntegerNode(lineGroupRecord.get("pointCount")).maximum();
			}
		}

// Get Cartesian bounding box to scan.
 
		if(scan.isDefined("cartesianBounds"))
		{
			StructureNode bbox(scan.get("cartesianBounds"));
			if( bbox.get("xMinimum").type() == E57_SCALED_INTEGER ) {
				data3DHeader.cartesianBounds.xMinimum = (double) ScaledIntegerNode(bbox.get("xMinimum")).scaledValue();
				data3DHeader.cartesianBounds.xMaximum = (double) ScaledIntegerNode(bbox.get("xMaximum")).scaledValue();
				data3DHeader.cartesianBounds.yMinimum = (double) ScaledIntegerNode(bbox.get("yMinimum")).scaledValue();
				data3DHeader.cartesianBounds.yMaximum = (double) ScaledIntegerNode(bbox.get("yMaximum")).scaledValue();
				data3DHeader.cartesianBounds.zMinimum = (double) ScaledIntegerNode(bbox.get("zMinimum")).scaledValue();
				data3DHeader.cartesianBounds.zMaximum = (double) ScaledIntegerNode(bbox.get("zMaximum")).scaledValue();
			}
			else if( bbox.get("xMinimum").type() == E57_FLOAT ){
				data3DHeader.cartesianBounds.xMinimum = FloatNode(bbox.get("xMinimum")).value();
				data3DHeader.cartesianBounds.xMaximum = FloatNode(bbox.get("xMaximum")).value();
				data3DHeader.cartesianBounds.yMinimum = FloatNode(bbox.get("yMinimum")).value();
				data3DHeader.cartesianBounds.yMaximum = FloatNode(bbox.get("yMaximum")).value();
				data3DHeader.cartesianBounds.zMinimum = FloatNode(bbox.get("zMinimum")).value();
				data3DHeader.cartesianBounds.zMaximum = FloatNode(bbox.get("zMaximum")).value();
			}
		}

		if(scan.isDefined("sphericalBounds"))
		{
			StructureNode sbox(scan.get("sphericalBounds"));
			if( sbox.get("rangeMinimum").type() == E57_SCALED_INTEGER ) {
				data3DHeader.sphericalBounds.rangeMinimum = (double) ScaledIntegerNode(sbox.get("rangeMinimum")).scaledValue();
				data3DHeader.sphericalBounds.rangeMaximum = (double) ScaledIntegerNode(sbox.get("rangeMaximum")).scaledValue();
			}
			else if( sbox.get("rangeMinimum").type() == E57_FLOAT ){
				data3DHeader.sphericalBounds.rangeMinimum = FloatNode(sbox.get("rangeMinimum")).value();
				data3DHeader.sphericalBounds.rangeMaximum = FloatNode(sbox.get("rangeMaximum")).value();
			}

			if( sbox.get("elevationMinimum").type() == E57_SCALED_INTEGER ) {
				data3DHeader.sphericalBounds.elevationMinimum = (double) ScaledIntegerNode(sbox.get("elevationMinimum")).scaledValue();
				data3DHeader.sphericalBounds.elevationMaximum = (double) ScaledIntegerNode(sbox.get("elevationMaximum")).scaledValue();
			}
			else if( sbox.get("elevationMinimum").type() == E57_FLOAT ){
				data3DHeader.sphericalBounds.elevationMinimum = FloatNode(sbox.get("elevationMinimum")).value();
				data3DHeader.sphericalBounds.elevationMaximum = FloatNode(sbox.get("elevationMaximum")).value();
			}

			if( sbox.get("azimuthStart").type() == E57_SCALED_INTEGER ) {
				data3DHeader.sphericalBounds.azimuthStart = (double) ScaledIntegerNode(sbox.get("azimuthStart")).scaledValue();
				data3DHeader.sphericalBounds.azimuthEnd = (double) ScaledIntegerNode(sbox.get("azimuthEnd")).scaledValue();
			}
			else if( sbox.get("azimuthStart").type() == E57_FLOAT ){
				data3DHeader.sphericalBounds.azimuthStart = FloatNode(sbox.get("azimuthStart")).value();
				data3DHeader.sphericalBounds.azimuthEnd = FloatNode(sbox.get("azimuthEnd")).value();
			}
		}

// Get pose structure for scan.

		if(scan.isDefined("pose"))
		{
			StructureNode pose(scan.get("pose"));
			if(pose.isDefined("rotation"))
			{
				StructureNode rotation(pose.get("rotation"));
				data3DHeader.pose.rotation.w = FloatNode(rotation.get("w")).value();
				data3DHeader.pose.rotation.x = FloatNode(rotation.get("x")).value();
				data3DHeader.pose.rotation.y = FloatNode(rotation.get("y")).value();
				data3DHeader.pose.rotation.z = FloatNode(rotation.get("z")).value();
			}
			if(pose.isDefined("translation"))
			{
				StructureNode translation(pose.get("translation"));  
				data3DHeader.pose.translation.x = FloatNode(translation.get("x")).value();
				data3DHeader.pose.translation.y = FloatNode(translation.get("y")).value();
				data3DHeader.pose.translation.z = FloatNode(translation.get("z")).value();
			}
		}
 
// Get start/stop acquisition times to scan.

		if(scan.isDefined("acquisitionStart"))
		{
			StructureNode acquisitionStart(scan.get("acquisitionStart"));
			data3DHeader.acquisitionStart.dateTimeValue = 
				FloatNode(acquisitionStart.get("dateTimeValue")).value();
			data3DHeader.acquisitionStart.isAtomicClockReferenced = (int32_t)
				IntegerNode(acquisitionStart.get("isAtomicClockReferenced")).value();
		}

		if(scan.isDefined("acquisitionEnd"))
		{
			StructureNode acquisitionEnd(scan.get("acquisitionEnd"));
			data3DHeader.acquisitionEnd.dateTimeValue = 
				FloatNode(acquisitionEnd.get("dateTimeValue")).value();
			data3DHeader.acquisitionEnd.isAtomicClockReferenced = (int32_t)
				IntegerNode(acquisitionEnd.get("isAtomicClockReferenced")).value();
		}

// Get a prototype of datatypes that will be stored in points record.

		data3DHeader.pointFields.cartesianXField = proto.isDefined("cartesianX");
		data3DHeader.pointFields.cartesianYField = proto.isDefined("cartesianY");
		data3DHeader.pointFields.cartesianZField = proto.isDefined("cartesianZ");
		data3DHeader.pointFields.cartesianInvalidStateField = proto.isDefined("cartesianInvalidState");

		data3DHeader.pointFields.pointRangeScaledInteger = 0.; //FloatNode
		data3DHeader.pointFields.pointRangeMinimum = 0.;
		data3DHeader.pointFields.pointRangeMaximum = 0.; 

		if( proto.isDefined("cartesianX")) {
			if( proto.get("cartesianX").type() == E57_SCALED_INTEGER ) {
				double scale = ScaledIntegerNode(proto.get("cartesianX")).scale();
				double offset = ScaledIntegerNode(proto.get("cartesianX")).offset();
				int64_t minimum = ScaledIntegerNode(proto.get("cartesianX")).minimum();
				int64_t maximum = ScaledIntegerNode(proto.get("cartesianX")).maximum();
				data3DHeader.pointFields.pointRangeMinimum = (double)
					minimum * scale + offset;	
				data3DHeader.pointFields.pointRangeMaximum = (double)
					maximum * scale + offset;
				data3DHeader.pointFields.pointRangeScaledInteger = scale;

			} else if( proto.get("cartesianX").type() == E57_FLOAT ){
				data3DHeader.pointFields.pointRangeMinimum =
					FloatNode(proto.get("cartesianX")).minimum();
				data3DHeader.pointFields.pointRangeMaximum =
					FloatNode(proto.get("cartesianX")).maximum();
			}
		} 
		else if( proto.isDefined("sphericalRange")) {
			if( proto.get("sphericalRange").type() == E57_SCALED_INTEGER ) {
				double scale = ScaledIntegerNode(proto.get("sphericalRange")).scale();
				double offset = ScaledIntegerNode(proto.get("sphericalRange")).offset();
				int64_t minimum = ScaledIntegerNode(proto.get("sphericalRange")).minimum();
				int64_t maximum = ScaledIntegerNode(proto.get("sphericalRange")).maximum();
				data3DHeader.pointFields.pointRangeMinimum = (double)
					minimum * scale + offset;	
				data3DHeader.pointFields.pointRangeMaximum = (double)
					maximum * scale + offset;
				data3DHeader.pointFields.pointRangeScaledInteger = scale;

			} else if( proto.get("sphericalRange").type() == E57_FLOAT ){
				data3DHeader.pointFields.pointRangeMinimum =
					FloatNode(proto.get("sphericalRange")).minimum();
				data3DHeader.pointFields.pointRangeMaximum =
					FloatNode(proto.get("sphericalRange")).maximum();
			}
		}

		data3DHeader.pointFields.sphericalRangeField = proto.isDefined("sphericalRange");
		data3DHeader.pointFields.sphericalAzimuthField = proto.isDefined("sphericalAzimuth");
		data3DHeader.pointFields.sphericalElevationField = proto.isDefined("sphericalElevation");
		data3DHeader.pointFields.sphericalInvalidStateField = proto.isDefined("sphericalInvalidState");

		data3DHeader.pointFields.angleScaledInteger = 0.; //FloatNode
		data3DHeader.pointFields.angleMinimum = 0.;
		data3DHeader.pointFields.angleMaximum = 0.;

		if(proto.isDefined("sphericalAzimuth")) {
			if( proto.get("sphericalAzimuth").type() == E57_SCALED_INTEGER) {
				double scale = ScaledIntegerNode(proto.get("sphericalAzimuth")).scale();
				double offset = ScaledIntegerNode(proto.get("sphericalAzimuth")).offset();
				int64_t minimum = ScaledIntegerNode(proto.get("sphericalAzimuth")).minimum();
				int64_t maximum = ScaledIntegerNode(proto.get("sphericalAzimuth")).maximum();
				data3DHeader.pointFields.angleMinimum = (double)
					minimum * scale + offset;	
				data3DHeader.pointFields.angleMaximum = (double)
					maximum * scale + offset;
				data3DHeader.pointFields.angleScaledInteger = scale;

			} else if( proto.get("sphericalAzimuth").type() == E57_FLOAT ){
				data3DHeader.pointFields.angleMinimum =
					FloatNode(proto.get("sphericalAzimuth")).minimum();
				data3DHeader.pointFields.angleMaximum =
					FloatNode(proto.get("sphericalAzimuth")).maximum();
			}
		}

		data3DHeader.pointFields.rowIndexField = proto.isDefined("rowIndex");
		data3DHeader.pointFields.columnIndexField = proto.isDefined("columnIndex");
		data3DHeader.pointFields.rowIndexMaximum = 0;
		data3DHeader.pointFields.columnIndexMaximum = 0;

		if( proto.isDefined("rowIndex"))
			data3DHeader.pointFields.rowIndexMaximum = (uint32_t)
				IntegerNode(proto.get("rowIndex")).maximum();

		if( proto.isDefined("columnIndex"))
			data3DHeader.pointFields.columnIndexMaximum = (uint32_t)
				IntegerNode(proto.get("columnIndex")).maximum();

		data3DHeader.pointFields.returnIndexField = proto.isDefined("returnIndex");
		data3DHeader.pointFields.returnCountField = proto.isDefined("returnCount");
		data3DHeader.pointFields.returnMaximum = 0;

		if( proto.isDefined("returnIndex"))
			data3DHeader.pointFields.returnMaximum = (uint8_t)
				IntegerNode(proto.get("returnIndex")).maximum();

		data3DHeader.pointFields.timeStampField = proto.isDefined("timeStamp");
		data3DHeader.pointFields.isTimeStampInvalidField = proto.isDefined("isTimeStampInvalid");
		data3DHeader.pointFields.timeMaximum = 0.;

		if(proto.isDefined("timeStamp")) {
			if( proto.get("timeStamp").type() == E57_INTEGER)
				data3DHeader.pointFields.timeMaximum = (double) IntegerNode(proto.get("timeStamp")).maximum();
			else if( proto.get("timeStamp").type() == E57_FLOAT)
				data3DHeader.pointFields.timeMaximum = FloatNode(proto.get("timeStamp")).maximum();
		}
	
		data3DHeader.pointFields.intensityField = proto.isDefined("intensity");
		data3DHeader.pointFields.isIntensityInvalidField = proto.isDefined("isIntensityInvalid");
		data3DHeader.intensityLimits.intensityMinimum = 0.;
		data3DHeader.intensityLimits.intensityMaximum = 0.;
		data3DHeader.pointFields.intensityScaledInteger = 0.;

		if(scan.isDefined("intensityLimits"))
		{
			StructureNode intbox(scan.get("intensityLimits"));
			if( intbox.get("intensityMaximum").type() == E57_SCALED_INTEGER ) {
				data3DHeader.intensityLimits.intensityMaximum = (double)
					ScaledIntegerNode(intbox.get("intensityMaximum")).scaledValue();
				data3DHeader.intensityLimits.intensityMinimum = (double)
					ScaledIntegerNode(intbox.get("intensityMinimum")).scaledValue();
			}
			else if( intbox.get("intensityMaximum").type() == E57_FLOAT ){
				data3DHeader.intensityLimits.intensityMaximum =
					FloatNode(intbox.get("intensityMaximum")).value();
				data3DHeader.intensityLimits.intensityMinimum =
					FloatNode(intbox.get("intensityMinimum")).value();
			}
			else if( intbox.get("intensityMaximum").type() == E57_INTEGER) {
				data3DHeader.intensityLimits.intensityMaximum = (double)
					IntegerNode(intbox.get("intensityMaximum")).value();
				data3DHeader.intensityLimits.intensityMinimum = (double)
					IntegerNode(intbox.get("intensityMinimum")).value();
			}
		}
		if(proto.isDefined("intensity"))
		{
			if(proto.get("intensity").type() == E57_INTEGER) {
				if(data3DHeader.intensityLimits.intensityMaximum == 0.){
					data3DHeader.intensityLimits.intensityMinimum = (double)
						IntegerNode(proto.get("intensity")).minimum();
					data3DHeader.intensityLimits.intensityMaximum = (double)
						IntegerNode(proto.get("intensity")).maximum();
				}
				data3DHeader.pointFields.intensityScaledInteger = -1.;

			} else if(proto.get("intensity").type() == E57_SCALED_INTEGER) {
				double scale = ScaledIntegerNode(proto.get("intensity")).scale();
				double offset = ScaledIntegerNode(proto.get("intensity")).offset();

				if(data3DHeader.intensityLimits.intensityMaximum == 0.){
					int64_t minimum = ScaledIntegerNode(proto.get("intensity")).minimum();
					int64_t maximum = ScaledIntegerNode(proto.get("intensity")).maximum();
					data3DHeader.intensityLimits.intensityMinimum = (double)
						minimum * scale + offset;	
					data3DHeader.intensityLimits.intensityMaximum = (double)
						maximum * scale + offset;
				}
				data3DHeader.pointFields.intensityScaledInteger = scale;
		
			} else if(proto.get("intensity").type() == E57_FLOAT) {
				if(data3DHeader.intensityLimits.intensityMaximum == 0.){
					data3DHeader.intensityLimits.intensityMinimum = 
						FloatNode(proto.get("intensity")).minimum();
					data3DHeader.intensityLimits.intensityMaximum = 
						FloatNode(proto.get("intensity")).maximum();
				}
			}
		}

		data3DHeader.pointFields.colorRedField = proto.isDefined("colorRed");
		data3DHeader.pointFields.colorGreenField = proto.isDefined("colorGreen");
		data3DHeader.pointFields.colorBlueField = proto.isDefined("colorBlue");
		data3DHeader.pointFields.isColorInvalidField = proto.isDefined("isColorInvalid");

		data3DHeader.colorLimits.colorRedMinimum = 0.;
		data3DHeader.colorLimits.colorRedMaximum = 0.;
		data3DHeader.colorLimits.colorGreenMinimum = 0.;
		data3DHeader.colorLimits.colorGreenMaximum = 0.;
		data3DHeader.colorLimits.colorBlueMinimum = 0.;
		data3DHeader.colorLimits.colorBlueMaximum = 0.;

		if(scan.isDefined("colorLimits"))
		{
			StructureNode colorbox(scan.get("colorLimits"));
			if( colorbox.get("colorRedMaximum").type() == E57_SCALED_INTEGER ) {
				data3DHeader.colorLimits.colorRedMaximum = (double) ScaledIntegerNode(colorbox.get("colorRedMaximum")).scaledValue();
				data3DHeader.colorLimits.colorRedMinimum = (double) ScaledIntegerNode(colorbox.get("colorRedMinimum")).scaledValue();
				data3DHeader.colorLimits.colorGreenMaximum = (double) ScaledIntegerNode(colorbox.get("colorGreenMaximum")).scaledValue();
				data3DHeader.colorLimits.colorGreenMinimum = (double) ScaledIntegerNode(colorbox.get("colorGreenMinimum")).scaledValue();
				data3DHeader.colorLimits.colorBlueMaximum = (double) ScaledIntegerNode(colorbox.get("colorBlueMaximum")).scaledValue();
				data3DHeader.colorLimits.colorBlueMinimum = (double) ScaledIntegerNode(colorbox.get("colorBlueMinimum")).scaledValue();
			}
			else if( colorbox.get("colorRedMaximum").type() == E57_FLOAT ){
				data3DHeader.colorLimits.colorRedMaximum = FloatNode(colorbox.get("colorRedMaximum")).value();
				data3DHeader.colorLimits.colorRedMinimum = FloatNode(colorbox.get("colorRedMinimum")).value();
				data3DHeader.colorLimits.colorGreenMaximum = FloatNode(colorbox.get("colorGreenMaximum")).value();
				data3DHeader.colorLimits.colorGreenMinimum = FloatNode(colorbox.get("colorGreenMinimum")).value();
				data3DHeader.colorLimits.colorBlueMaximum = FloatNode(colorbox.get("colorBlueMaximum")).value();
				data3DHeader.colorLimits.colorBlueMinimum = FloatNode(colorbox.get("colorBlueMinimum")).value();
			}
			else if( colorbox.get("colorRedMaximum").type() == E57_INTEGER) {
				data3DHeader.colorLimits.colorRedMaximum = (double) IntegerNode(colorbox.get("colorRedMaximum")).value();
				data3DHeader.colorLimits.colorRedMinimum = (double) IntegerNode(colorbox.get("colorRedMinimum")).value();
				data3DHeader.colorLimits.colorGreenMaximum = (double) IntegerNode(colorbox.get("colorGreenMaximum")).value();
				data3DHeader.colorLimits.colorGreenMinimum = (double) IntegerNode(colorbox.get("colorGreenMinimum")).value();
				data3DHeader.colorLimits.colorBlueMaximum = (double) IntegerNode(colorbox.get("colorBlueMaximum")).value();
				data3DHeader.colorLimits.colorBlueMinimum = (double) IntegerNode(colorbox.get("colorBlueMinimum")).value();
			}
		}

		if((data3DHeader.colorLimits.colorRedMaximum == 0.) && proto.isDefined("colorRed"))
		{
			if(proto.get("colorRed").type() == E57_INTEGER) {
				data3DHeader.colorLimits.colorRedMinimum = (uint16_t)
					IntegerNode(proto.get("colorRed")).minimum();
				data3DHeader.colorLimits.colorRedMaximum = (uint16_t)
					IntegerNode(proto.get("colorRed")).maximum();
			}
			else if(proto.get("colorRed").type() == E57_FLOAT) {
				data3DHeader.colorLimits.colorRedMinimum = (uint16_t)
					FloatNode(proto.get("colorRed")).minimum();
				data3DHeader.colorLimits.colorRedMaximum = (uint16_t)
					FloatNode(proto.get("colorRed")).maximum();
			}
			else if(proto.get("colorRed").type() == E57_SCALED_INTEGER) {
				double scale = ScaledIntegerNode(proto.get("colorRed")).scale();
				double offset = ScaledIntegerNode(proto.get("colorRed")).offset();
				int64_t minimum = ScaledIntegerNode(proto.get("colorRed")).minimum();
				int64_t maximum = ScaledIntegerNode(proto.get("colorRed")).maximum();
				data3DHeader.colorLimits.colorRedMinimum = (uint16_t)
						minimum * scale + offset;	
				data3DHeader.colorLimits.colorRedMaximum = (uint16_t)
						maximum * scale + offset;
			}
		}
		if((data3DHeader.colorLimits.colorGreenMaximum == 0.) && proto.isDefined("colorGreen"))
		{
			if(proto.get("colorGreen").type() == E57_INTEGER) {
				data3DHeader.colorLimits.colorGreenMinimum = (uint16_t)
					IntegerNode(proto.get("colorGreen")).minimum();
				data3DHeader.colorLimits.colorGreenMaximum = (uint16_t)
					IntegerNode(proto.get("colorGreen")).maximum();
			}
			else if(proto.get("colorGreen").type() == E57_FLOAT) {
				data3DHeader.colorLimits.colorGreenMinimum = (uint16_t)
					FloatNode(proto.get("colorGreen")).minimum();
				data3DHeader.colorLimits.colorGreenMaximum = (uint16_t)
					FloatNode(proto.get("colorGreen")).maximum();
			}
			else if(proto.get("colorGreen").type() == E57_SCALED_INTEGER) {
				double scale = ScaledIntegerNode(proto.get("colorGreen")).scale();
				double offset = ScaledIntegerNode(proto.get("colorGreen")).offset();
				int64_t minimum = ScaledIntegerNode(proto.get("colorGreen")).minimum();
				int64_t maximum = ScaledIntegerNode(proto.get("colorGreen")).maximum();
				data3DHeader.colorLimits.colorGreenMinimum = (uint16_t)
						minimum * scale + offset;	
				data3DHeader.colorLimits.colorGreenMaximum = (uint16_t)
						maximum * scale + offset;
			}
		}
		if((data3DHeader.colorLimits.colorBlueMaximum == 0.) && proto.isDefined("colorBlue"))
		{
			if( proto.get("colorBlue").type() == E57_INTEGER) {
				data3DHeader.colorLimits.colorBlueMinimum = (uint16_t)
					IntegerNode(proto.get("colorBlue")).minimum();
				data3DHeader.colorLimits.colorBlueMaximum = (uint16_t)
					IntegerNode(proto.get("colorBlue")).maximum();
			}
			else if( proto.get("colorBlue").type() ==E57_FLOAT) {
				data3DHeader.colorLimits.colorBlueMinimum = (uint16_t)
					FloatNode(proto.get("colorBlue")).minimum();
				data3DHeader.colorLimits.colorBlueMaximum = (uint16_t)
					FloatNode(proto.get("colorBlue")).maximum();
			}
			else if(proto.get("colorBlue").type() == E57_SCALED_INTEGER) {
				double scale = ScaledIntegerNode(proto.get("colorBlue")).scale();
				double offset = ScaledIntegerNode(proto.get("colorBlue")).offset();
				int64_t minimum = ScaledIntegerNode(proto.get("colorBlue")).minimum();
				int64_t maximum = ScaledIntegerNode(proto.get("colorBlue")).maximum();
				data3DHeader.colorLimits.colorRedMinimum = (uint16_t)
						minimum * scale + offset;	
				data3DHeader.colorLimits.colorRedMaximum = (uint16_t)
						maximum * scale + offset;
			}
		}
		return true;
	}
	return false;
};

//! This function returns the size of the point data
bool	ReaderImpl :: GetData3DSizes(
	int32_t		dataIndex,	//!< image block index
	int64_t &	row,		//!< image row size
	int64_t &	column,		//!< image column size
	int64_t &	pointsSize,	//!< image total point count
	int64_t &	groupsSize,	//!< image total number of groups
	int64_t &	countSize,	//!< This is the maximum point count per group
	bool &		bColumnIndex	//!< This indicates that the idElementName is "columnIndex"
	)
{
	if(IsOpen())
	{
		row = 0;
		column = 0;
		pointsSize = 0;
		groupsSize = 0;
		int64_t elementSize = 0;
		countSize = 0;
		bColumnIndex = false;

		if( (dataIndex < 0) || (dataIndex >= data3D_.childCount()))
			return false;

		StructureNode scan(data3D_.get(dataIndex));

		CompressedVectorNode points(scan.get("points"));
		pointsSize = points.childCount();

		if(scan.isDefined("indexBounds"))
		{
			StructureNode indexBounds(scan.get("indexBounds"));
			if(indexBounds.isDefined("columnMaximum"))
				column = IntegerNode(indexBounds.get("columnMaximum")).value() -
						 IntegerNode(indexBounds.get("columnMinimum")).value() + 1;

			if(indexBounds.isDefined("rowMaximum"))
				row = IntegerNode(indexBounds.get("rowMaximum")).value() -
					  IntegerNode(indexBounds.get("rowMinimum")).value() + 1;
		}

		if(scan.isDefined("pointGroupingSchemes"))
		{
			StructureNode pointGroupingSchemes(scan.get("pointGroupingSchemes"));
			if(pointGroupingSchemes.isDefined("groupingByLine"))
			{
				StructureNode groupingByLine(pointGroupingSchemes.get("groupingByLine"));

				StringNode idElementName(groupingByLine.get("idElementName"));
				if(idElementName.value().compare("columnIndex") == 0)
					bColumnIndex = true;

				CompressedVectorNode	groups(groupingByLine.get("groups"));
				groupsSize = groups.childCount();

				StructureNode lineGroupRecord(groups.prototype());

				if(lineGroupRecord.isDefined("idElementValue"))
					elementSize = IntegerNode(lineGroupRecord.get("idElementValue")).maximum() - 
								  IntegerNode(lineGroupRecord.get("idElementValue")).minimum() + 1;
				else if(bColumnIndex)
					elementSize = column;
				else
					elementSize = row;

				if(lineGroupRecord.isDefined("pointCount"))
					countSize = IntegerNode(lineGroupRecord.get("pointCount")).maximum();
				else if(bColumnIndex)
					countSize = row;
				else
					countSize = column;
			}
		}
// if indexBounds is not given
		if( row == 0)
		{
			if(bColumnIndex)
				row = countSize;
			else
				row = elementSize;
		}
		if( column == 0)
		{
			if(bColumnIndex)
				column = elementSize;
			else
				column = countSize;
		}
		return true;
	}
	return false;
};

//! This funtion writes out the group data
bool	ReaderImpl :: ReadData3DGroupsData(
						int32_t		dataIndex,			//!< data block index given by the NewData3D
						int32_t		groupCount,			//!< size of each of the buffers given
						int64_t*	idElementValue,		//!< index for this group
						int64_t*	startPointIndex,	//!< Starting index in to the "points" data vector for the groups
						int64_t*	pointCount			//!< size of the groups given
						)								//!< \return Return true if sucessful, false otherwise
{
	if( (dataIndex < 0) || (dataIndex >= data3D_.childCount()))
		return false;

	StructureNode scan(data3D_.get(dataIndex));
	if(scan.isDefined("pointGroupingSchemes"))
	{
		StructureNode pointGroupingSchemes(scan.get("pointGroupingSchemes"));
		if(pointGroupingSchemes.isDefined("groupingByLine"))
		{
			StructureNode groupingByLine(pointGroupingSchemes.get("groupingByLine"));

			StringNode	idElementName(groupingByLine.get("idElementName"));
			CompressedVectorNode groups(groupingByLine.get("groups"));
			StructureNode lineGroupRecord(groups.prototype()); //not used here

			int64_t protoCount = lineGroupRecord.childCount();
			int64_t protoIndex;
			vector<SourceDestBuffer> groupSDBuffers;

			for( protoIndex = 0; protoIndex < protoCount; protoIndex++)
			{
				ustring		name = lineGroupRecord.get(protoIndex).elementName();
	
				if((name.compare("idElementValue") == 0) && lineGroupRecord.isDefined("idElementValue") && (idElementValue != NULL))
					groupSDBuffers.push_back(SourceDestBuffer(imf_, "idElementValue",
						idElementValue,   (unsigned) groupCount, true));

				if((name.compare("startPointIndex") == 0) && lineGroupRecord.isDefined("startPointIndex") && (startPointIndex != NULL))
					groupSDBuffers.push_back(SourceDestBuffer(imf_, "startPointIndex",
						startPointIndex,  (unsigned) groupCount, true));

				if((name.compare("pointCount") == 0) && lineGroupRecord.isDefined("pointCount") && (pointCount != NULL))
					groupSDBuffers.push_back(SourceDestBuffer(imf_, "pointCount",
						pointCount,       (unsigned) groupCount, true));
			}

			CompressedVectorReader reader = groups.reader(groupSDBuffers);

			reader.read();
			reader.close();
			return true;
		}
	}
	return false;
};

//! This function returns the point data fields fetched in single call
//* All the non-NULL buffers in the call below have number of elements = count */

CompressedVectorReader	ReaderImpl :: SetUpData3DPointsData(
	int32_t		dataIndex,
	int64_t		count,
	double*		cartesianX,			//!< pointer to a buffer with the X coordinate (in meters) of the point in Cartesian coordinates
	double*		cartesianY,			//!< pointer to a buffer with the Y coordinate (in meters) of the point in Cartesian coordinates
	double*		cartesianZ,			//!< pointer to a buffer with the Z coordinate (in meters) of the point in Cartesian coordinates
	int8_t*		cartesianInvalidState,	//!< Value = 0 if the point is considered valid, 1 otherwise

	double*		intensity,			//!< pointer to a buffer with the Point response intensity. Unit is unspecified
	int8_t*		isIntensityInvalid,	//!< Value = 0 if the intensity is considered valid, 1 otherwise

	uint16_t*	colorRed,			//!< pointer to a buffer with the Red color coefficient. Unit is unspecified
	uint16_t*	colorGreen,			//!< pointer to a buffer with the Green color coefficient. Unit is unspecified
	uint16_t*	colorBlue,			//!< pointer to a buffer with the Blue color coefficient. Unit is unspecified
	int8_t*		isColorInvalid,		//!< Value = 0 if the color is considered valid, 1 otherwise

	double*		sphericalRange,		//!< pointer to a buffer with the range (in meters) of points in spherical coordinates. Shall be non-negative
	double*		sphericalAzimuth,	//!< pointer to a buffer with the Azimuth angle (in radians) of point in spherical coordinates
	double*		sphericalElevation,	//!< pointer to a buffer with the Elevation angle (in radians) of point in spherical coordinates
	int8_t*		sphericalInvalidState, //!< Value = 0 if the range is considered valid, 1 otherwise

	int32_t*	rowIndex,			//!< pointer to a buffer with the row number of point (zero based). This is useful for data that is stored in a regular grid.Shall be in the interval (0, 2^63).
	int32_t*	columnIndex,		//!< pointer to a buffer with the column number of point (zero based). This is useful for data that is stored in a regular grid. Shall be in the interval (0, 2^63).
	int8_t*		returnIndex,		//!< pointer to a buffer with the number of this return (zero based). That is, 0 is the first return, 1 is the second, and so on. Shall be in the interval (0, returnCount). Only for multi-return sensors. 
	int8_t*		returnCount,		//!< pointer to a buffer with the total number of returns for the pulse that this corresponds to. Shall be in the interval (0, 2^63). Only for multi-return sensors. 

	double*		timeStamp,			//!< pointer to a buffer with the time (in seconds) since the start time for the data, which is given by acquisitionStart in the parent Data3D Structure. Shall be non-negative
	int8_t*		isTimeStampInvalid	//!< Value = 0 if the timeStamp is considered valid, 1 otherwise
	)
{
	int64_t		readCount = 0;

	StructureNode scan(data3D_.get(dataIndex));
	CompressedVectorNode points(scan.get("points"));
	StructureNode proto(points.prototype());

	int64_t protoCount = proto.childCount();
	int64_t protoIndex;

	vector<SourceDestBuffer> destBuffers;

	for( protoIndex = 0; protoIndex < protoCount; protoIndex++)
	{
		ustring		name = proto.get(protoIndex).elementName();
		NodeType	type = proto.get(protoIndex).type();
		bool		scaled = type == E57_SCALED_INTEGER ? true : false;

		if((name.compare("cartesianX") == 0) && proto.isDefined("cartesianX") && (cartesianX != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "cartesianX",
				cartesianX,  (unsigned) count, true, scaled));
		else if((name.compare("cartesianY") == 0) && proto.isDefined("cartesianY") && (cartesianY != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "cartesianY",
				cartesianY,  (unsigned) count, true,scaled));
		else if((name.compare("cartesianZ") == 0) && proto.isDefined("cartesianZ") && (cartesianZ != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "cartesianZ",
				cartesianZ,  (unsigned) count, true, scaled));
		else if((name.compare("cartesianInvalidState") == 0) && proto.isDefined("cartesianInvalidState") && (cartesianInvalidState != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "cartesianInvalidState",
				cartesianInvalidState,       (unsigned) count, true));

		else if((name.compare("sphericalRange") == 0) && proto.isDefined("sphericalRange") && (sphericalRange != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "sphericalRange",
				sphericalRange,  (unsigned) count, true, scaled));
		else if((name.compare("sphericalAzimuth") == 0) && proto.isDefined("sphericalAzimuth") && (sphericalAzimuth != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "sphericalAzimuth",
				sphericalAzimuth,  (unsigned) count, true, scaled));
		else if((name.compare("sphericalElevation") == 0) && proto.isDefined("sphericalElevation") && (sphericalElevation != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "sphericalElevation",
				sphericalElevation,  (unsigned) count, true, scaled));
		else if((name.compare("sphericalInvalidState") == 0) && proto.isDefined("sphericalInvalidState") && (sphericalInvalidState != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "sphericalInvalidState",
				sphericalInvalidState,       (unsigned) count, true));

		else if((name.compare("rowIndex") == 0) && proto.isDefined("rowIndex") && (rowIndex != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "rowIndex",
				rowIndex,    (unsigned) count, true));
		else if((name.compare("columnIndex") == 0) && proto.isDefined("columnIndex") && (columnIndex != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "columnIndex",
				columnIndex, (unsigned) count, true));
		else if((name.compare("returnIndex") == 0) && proto.isDefined("returnIndex") && (returnIndex != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "returnIndex",
				returnIndex, (unsigned) count, true));
		else if((name.compare("returnCount") == 0) && proto.isDefined("returnCount") && (returnCount != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "returnCount",
				returnCount, (unsigned) count, true));

		else if((name.compare("timeStamp") == 0) && proto.isDefined("timeStamp") && (timeStamp != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "timeStamp",
				timeStamp,   (unsigned) count, true));
		else if((name.compare("isTimeStampInvalid") == 0) && proto.isDefined("isTimeStampInvalid") && (isTimeStampInvalid != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "isTimeStampInvalid",
				isTimeStampInvalid,       (unsigned) count, true));

		else if((name.compare("intensity") == 0) && proto.isDefined("intensity") && (intensity != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "intensity",   intensity,
				(unsigned) count, true, scaled));
		else if((name.compare("isIntensityInvalid") == 0) && proto.isDefined("isIntensityInvalid") && (isIntensityInvalid != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "isIntensityInvalid",
				isIntensityInvalid,       (unsigned) count, true));

		else if((name.compare("colorRed") == 0) && proto.isDefined("colorRed") && (colorRed != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "colorRed",
				colorRed,    (unsigned) count, true, scaled));
		else if((name.compare("colorGreen") == 0) && proto.isDefined("colorGreen") && (colorGreen != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "colorGreen",
				colorGreen,  (unsigned) count, true, scaled));
		else if((name.compare("colorBlue") == 0) && proto.isDefined("colorBlue") && (colorBlue != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "colorBlue",
				colorBlue,   (unsigned) count, true, scaled));
		else if((name.compare("isColorInvalid") == 0) && proto.isDefined("isColorInvalid") && (isColorInvalid != NULL))
			destBuffers.push_back(SourceDestBuffer(imf_, "isColorInvalid",
				isColorInvalid,       (unsigned) count, true));
	}

	CompressedVectorReader reader = points.reader(destBuffers);

	return reader;
};

//#define TEST_EXTENSIONS
////////////////////////////////////////////////////////////////////
//
//	e57::Writer
//
//! This function is the constructor for the writer class
	WriterImpl::WriterImpl(
		const ustring & filePath,
		const ustring & coordinateMetadata)
	: imf_(filePath,"w")
	, root_(imf_.root())
	, data3D_(imf_,true)
	, images2D_(imf_,true)
{
/// We are using the E57 v1.0 data format standard fieldnames.
/// The standard fieldnames are used without an extension prefix (in the default namespace).
/// We explicitly register it for completeness (the reference implementaion would do it for us, if we didn't).
    imf_.extensionsAdd("", E57_V1_0_URI);

#ifdef TEST_EXTENSIONS
	imf_.extensionsAdd("ext","http://www.libe57.org/extensions.html");

	root_.set("ext:extDesc", StringNode(imf_, "This is a test string"));
#endif
// Set per-file properties.
/// Path names: "/formatName", "/majorVersion", "/minorVersion", "/coordinateMetadata"
	root_.set("formatName", StringNode(imf_, "ASTM E57 3D Imaging Data File"));
	root_.set("guid", StringNode(imf_, GetNewGuid()));

// Get ASTM version number supported by library, so can write it into file
	int astmMajor;
	int astmMinor;
	ustring libraryId;
	E57Utilities().getVersions(astmMajor, astmMinor, libraryId);

	root_.set("versionMajor", IntegerNode(imf_, astmMajor));
	root_.set("versionMinor", IntegerNode(imf_, astmMinor));
	root_.set("e57LibraryVersion", StringNode(imf_,libraryId));

// Save a dummy string for coordinate system.
/// Really should be a valid WKT string identifying the coordinate reference system (CRS).
    root_.set("coordinateMetadata", StringNode(imf_, coordinateMetadata));

// Create creationDateTime structure
/// Path name: "/creationDateTime
    StructureNode creationDateTime = StructureNode(imf_);
	creationDateTime.set("dateTimeValue", FloatNode(imf_, GetGPSTime()));
	creationDateTime.set("isAtomicClockReferenced", IntegerNode(imf_,0));
    root_.set("creationDateTime", creationDateTime);

	root_.set("data3D", data3D_);
	root_.set("images2D", images2D_);
};
//! This function is the destructor for the writer class
	WriterImpl::~WriterImpl(void)
{
	if(IsOpen())
		Close();
};
//! This function returns true if the file is open
bool	WriterImpl :: IsOpen(void)
{
	if(imf_.isOpen())
		return true;
	return false;
};

//! This function closes the file
bool	WriterImpl :: Close(void)
{
	if(IsOpen())
	{
		imf_.close();
		return true;
	}
	return false;
};
//! This function returns the file raw E57Root Structure Node
StructureNode	WriterImpl :: GetRawE57Root(void)
{
	return root_;
};	//!< /return Returns the E57Root StructureNode

//! This function returns the raw Data3D Vector Node
VectorNode		WriterImpl :: GetRawData3D(void)
{
	return data3D_;
};//!< /return Returns the raw Data3D VectorNode

//! This function returns the raw Images2D Vector Node
VectorNode		WriterImpl :: GetRawImages2D(void)
{
	return images2D_;
};	//!< /return Returns the raw Image2D VectorNode
////////////////////////////////////////////////////////////////////
//
//	Camera Image picture data
//

//! This function sets up the image2D header and positions the cursor
//* The user needs to config a Image2D structure with all the camera information before making this call. */
int32_t	WriterImpl :: NewImage2D( 
	Image2D &	image2DHeader	//!< pointer to the Image2D structure to receive the picture information
	)						//!< /return Returns the image2D index
{
	int32_t pos = -1;

	StructureNode image = StructureNode(imf_);
	images2D_.append(image);
	pos = (int32_t) images2D_.childCount() - 1;

	image.set("guid", StringNode(imf_, image2DHeader.guid));	//required

	if(!image2DHeader.name.empty())
		image.set("name", StringNode(imf_, image2DHeader.name));
	if(!image2DHeader.description.empty())
		image.set("description", StringNode(imf_, image2DHeader.description));

// Add various sensor and version strings to image.

	if(!image2DHeader.sensorVendor.empty())
		image.set("sensorVendor",           StringNode(imf_, image2DHeader.sensorVendor));
	if(!image2DHeader.sensorModel.empty())
		image.set("sensorModel",            StringNode(imf_, image2DHeader.sensorModel));
	if(!image2DHeader.sensorSerialNumber.empty())
		image.set("sensorSerialNumber",     StringNode(imf_, image2DHeader.sensorSerialNumber));

	if(!image2DHeader.associatedData3DGuid.empty())
		image.set("associatedData3DGuid", StringNode(imf_, image2DHeader.associatedData3DGuid));

	if(image2DHeader.acquisitionDateTime.dateTimeValue > 0.)
	{
		StructureNode acquisitionDateTime = StructureNode(imf_);
		image.set("acquisitionDateTime", acquisitionDateTime);
		acquisitionDateTime.set("dateTimeValue",
			FloatNode(imf_, image2DHeader.acquisitionDateTime.dateTimeValue));
		acquisitionDateTime.set("isAtomicClockReferenced",
			IntegerNode(imf_, image2DHeader.acquisitionDateTime.isAtomicClockReferenced));
	}

// Create pose structure for image.

	if( (image2DHeader.pose.rotation.w != 1.) ||
		(image2DHeader.pose.rotation.x != 0.) ||
		(image2DHeader.pose.rotation.y != 0.) ||
		(image2DHeader.pose.rotation.z != 0.) ||
		(image2DHeader.pose.translation.x != 0.) ||
		(image2DHeader.pose.translation.y != 0.) ||
		(image2DHeader.pose.translation.z != 0.) )
	{
		StructureNode pose = StructureNode(imf_);
		image.set("pose", pose);

		StructureNode rotation = StructureNode(imf_);
		pose.set("rotation", rotation);
		rotation.set("w", FloatNode(imf_, image2DHeader.pose.rotation.w));
		rotation.set("x", FloatNode(imf_, image2DHeader.pose.rotation.x));
		rotation.set("y", FloatNode(imf_, image2DHeader.pose.rotation.y));
		rotation.set("z", FloatNode(imf_, image2DHeader.pose.rotation.z));

		StructureNode translation = StructureNode(imf_);
		pose.set("translation", translation);
		translation.set("x", FloatNode(imf_, image2DHeader.pose.translation.x));
		translation.set("y", FloatNode(imf_, image2DHeader.pose.translation.y));
		translation.set("z", FloatNode(imf_, image2DHeader.pose.translation.z));
	}

#ifdef TEST_EXTENSIONS
	StructureNode extbox = StructureNode(imf_);
	extbox.set("ext:imageHeight", IntegerNode(imf_, image2DHeader.visualReferenceRepresentation.imageHeight));
	extbox.set("ext:imageWidth", IntegerNode(imf_,	image2DHeader.visualReferenceRepresentation.imageWidth));
	image.set("ext:imageSize", extbox);
	image.set("ext:extraStr", StringNode(imf_, "This is another extra string"));
	image.set("ext:extraFloat", FloatNode(imf_, 3.14159));
#endif

	if( image2DHeader.visualReferenceRepresentation.jpegImageSize > 0 ||
		image2DHeader.visualReferenceRepresentation.pngImageSize > 0)
	{
		StructureNode visualReferenceRepresentation = StructureNode(imf_);
		image.set("visualReferenceRepresentation", visualReferenceRepresentation);

		if( image2DHeader.visualReferenceRepresentation.jpegImageSize > 0)
			visualReferenceRepresentation.set("jpegImage",
				BlobNode(imf_,image2DHeader.visualReferenceRepresentation.jpegImageSize));
		else if( image2DHeader.visualReferenceRepresentation.pngImageSize > 0)
			visualReferenceRepresentation.set("pngImage",
				BlobNode(imf_,image2DHeader.visualReferenceRepresentation.pngImageSize));
		if( image2DHeader.visualReferenceRepresentation.imageMaskSize > 0)
			visualReferenceRepresentation.set("imageMask",
				BlobNode(imf_,image2DHeader.visualReferenceRepresentation.imageMaskSize));

		visualReferenceRepresentation.set("imageHeight", 
			IntegerNode(imf_, image2DHeader.visualReferenceRepresentation.imageHeight));
		visualReferenceRepresentation.set("imageWidth", 
			IntegerNode(imf_, image2DHeader.visualReferenceRepresentation.imageWidth));
	}
	else if( image2DHeader.pinholeRepresentation.jpegImageSize > 0 ||
		image2DHeader.pinholeRepresentation.pngImageSize > 0)
	{
		StructureNode pinholeRepresentation = StructureNode(imf_);
		image.set("pinholeRepresentation", pinholeRepresentation);

		if( image2DHeader.pinholeRepresentation.jpegImageSize > 0)
			pinholeRepresentation.set("jpegImage",
				BlobNode(imf_,image2DHeader.pinholeRepresentation.jpegImageSize));
		else if( image2DHeader.pinholeRepresentation.pngImageSize > 0)
			pinholeRepresentation.set("pngImage",
				BlobNode(imf_,image2DHeader.pinholeRepresentation.pngImageSize));
		if( image2DHeader.pinholeRepresentation.imageMaskSize > 0)
			pinholeRepresentation.set("imageMask",
				BlobNode(imf_,image2DHeader.pinholeRepresentation.imageMaskSize));

		pinholeRepresentation.set("focalLength", 
			FloatNode(imf_, image2DHeader.pinholeRepresentation.focalLength));
		pinholeRepresentation.set("imageHeight", 
			IntegerNode(imf_, image2DHeader.pinholeRepresentation.imageHeight));
		pinholeRepresentation.set("imageWidth", 
			IntegerNode(imf_, image2DHeader.pinholeRepresentation.imageWidth));
		pinholeRepresentation.set("pixelHeight", 
			FloatNode(imf_, image2DHeader.pinholeRepresentation.pixelHeight));
		pinholeRepresentation.set("pixelWidth", 
			FloatNode(imf_, image2DHeader.pinholeRepresentation.pixelWidth));
		pinholeRepresentation.set("principalPointX", 
			FloatNode(imf_, image2DHeader.pinholeRepresentation.principalPointX));
		pinholeRepresentation.set("principalPointY", 
			FloatNode(imf_, image2DHeader.pinholeRepresentation.principalPointY));
	}
	else if( image2DHeader.sphericalRepresentation.jpegImageSize > 0 ||
		image2DHeader.sphericalRepresentation.pngImageSize > 0)
	{
		StructureNode sphericalRepresentation = StructureNode(imf_);
		image.set("sphericalRepresentation", sphericalRepresentation);

		if( image2DHeader.sphericalRepresentation.jpegImageSize > 0)
			sphericalRepresentation.set("jpegImage",
				BlobNode(imf_,image2DHeader.sphericalRepresentation.jpegImageSize));
		else if( image2DHeader.sphericalRepresentation.pngImageSize > 0)
			sphericalRepresentation.set("pngImage",
				BlobNode(imf_,image2DHeader.sphericalRepresentation.pngImageSize));
		if( image2DHeader.sphericalRepresentation.imageMaskSize > 0)
			sphericalRepresentation.set("imageMask",
				BlobNode(imf_,image2DHeader.sphericalRepresentation.imageMaskSize));

		sphericalRepresentation.set("imageHeight", 
			IntegerNode(imf_, image2DHeader.sphericalRepresentation.imageHeight));
		sphericalRepresentation.set("imageWidth", 
			IntegerNode(imf_, image2DHeader.sphericalRepresentation.imageWidth));
		sphericalRepresentation.set("pixelHeight", 
			FloatNode(imf_, image2DHeader.sphericalRepresentation.pixelHeight));
		sphericalRepresentation.set("pixelWidth", 
			FloatNode(imf_, image2DHeader.sphericalRepresentation.pixelWidth));
	}
	else if( image2DHeader.cylindricalRepresentation.jpegImageSize > 0 ||
		image2DHeader.cylindricalRepresentation.pngImageSize > 0)
	{
		StructureNode cylindricalRepresentation = StructureNode(imf_);
		image.set("cylindricalRepresentation", cylindricalRepresentation);

		if( image2DHeader.cylindricalRepresentation.jpegImageSize > 0)
			cylindricalRepresentation.set("jpegImage",
				BlobNode(imf_,image2DHeader.cylindricalRepresentation.jpegImageSize));
		else if( image2DHeader.cylindricalRepresentation.pngImageSize > 0)
			cylindricalRepresentation.set("pngImage",
				BlobNode(imf_,image2DHeader.cylindricalRepresentation.pngImageSize));
		if( image2DHeader.cylindricalRepresentation.imageMaskSize > 0)
			cylindricalRepresentation.set("imageMask",
				BlobNode(imf_,image2DHeader.cylindricalRepresentation.imageMaskSize));

		cylindricalRepresentation.set("imageHeight", 
			IntegerNode(imf_, image2DHeader.cylindricalRepresentation.imageHeight));
		cylindricalRepresentation.set("imageWidth", 
			IntegerNode(imf_, image2DHeader.cylindricalRepresentation.imageWidth));
		cylindricalRepresentation.set("pixelHeight", 
			FloatNode(imf_, image2DHeader.cylindricalRepresentation.pixelHeight));
		cylindricalRepresentation.set("pixelWidth", 
			FloatNode(imf_, image2DHeader.cylindricalRepresentation.pixelWidth));
		cylindricalRepresentation.set("principalPointY", 
			FloatNode(imf_, image2DHeader.cylindricalRepresentation.principalPointY));
		cylindricalRepresentation.set("radius", 
			FloatNode(imf_, image2DHeader.cylindricalRepresentation.radius));
	}
	return pos;
};
//! This function reads one of the image blobs
int64_t WriterImpl :: WriteImage2DNode(
	e57::StructureNode		image,			//!< 1 of 3 projects or the visual
	e57::Image2DType		imageType,		//!< identifies the image format desired.
	void *					pBuffer,		//!< pointer the buffer
	int64_t					start,			//!< position in the block to start reading
	int64_t					count			//!< size of desired chuck or buffer size
	)										//!< /return Returns the number of bytes transferred.
{
	int64_t transferred = 0;
	switch(imageType)
	{
	case	E57_JPEG_IMAGE:
		{
			if(image.isDefined("jpegImage"))
			{
				BlobNode jpegImage(image.get("jpegImage"));
				jpegImage.write((uint8_t*) pBuffer, start, (size_t) count);
				transferred = count;
			}
			break;
		}
	case	E57_PNG_IMAGE:
		{
			if(	image.isDefined("pngImage"))
			{
				BlobNode pngImage(image.get("pngImage"));
				pngImage.write((uint8_t*) pBuffer, start, (size_t) count);
				transferred = count;
			}
			break;
		}
	case	E57_PNG_IMAGE_MASK:
		{
			if(	image.isDefined("imageMask"))
			{
				BlobNode imageMask(image.get("imageMask"));
				imageMask.write((uint8_t*) pBuffer, start, (size_t) count);
				transferred = count;
			}
			break;
		}
	};
	return transferred;
};
//! This function writes the block
int64_t	WriterImpl :: WriteImage2DData(
	int32_t					imageIndex,		//!< picture block index
	e57::Image2DType		imageType,		//!< identifies the image format desired.
	e57::Image2DProjection	imageProjection,//!< identifies the projection desired.
	void *					pBuffer,		//!< pointer the buffer
	int64_t					start,			//!< position in the block to start writing
	int64_t					count			//!< size of desired chuck or buffer size
	)										//!< /return Returns the number of bytes written
{
	if( (imageIndex < 0) || (imageIndex >= images2D_.childCount()))
		return 0;

	int64_t transferred = 0;
	StructureNode image(images2D_.get(imageIndex));

	switch(imageProjection)
	{
	case	E57_VISUAL:
		if(image.isDefined("visualReferenceRepresentation"))
		{
			StructureNode visualReferenceRepresentation(image.get("visualReferenceRepresentation"));
			transferred = WriteImage2DNode(visualReferenceRepresentation, imageType, pBuffer, start, count);
		}
		break;
	case	E57_PINHOLE:
		if(image.isDefined("pinholeRepresentation"))
		{
			StructureNode pinholeRepresentation(image.get("pinholeRepresentation"));
			transferred = WriteImage2DNode(pinholeRepresentation, imageType, pBuffer, start, count);
		}
		break;
	case	E57_SPHERICAL:
		if(image.isDefined("sphericalRepresentation"))
		{
			StructureNode sphericalRepresentation(image.get("sphericalRepresentation"));
			transferred = WriteImage2DNode(sphericalRepresentation, imageType, pBuffer, start, count);
		}
		break;
	case	E57_CYLINDRICAL:
		if(image.isDefined("cylindricalRepresentation"))
		{
			StructureNode cylindricalRepresentation(image.get("cylindricalRepresentation"));
			transferred = WriteImage2DNode(cylindricalRepresentation, imageType, pBuffer, start, count);
		}
		break;
	};
	return transferred;
};

//! This function sets up the Data3D header and positions the cursor for the binary data
//* The user needs to config a Data3D structure with all the scanning information before making this call. */

int32_t	WriterImpl :: NewData3D( 
	Data3D &	data3DHeader //!< pointer to the Data3D structure to receive the image information
	)	//!< /return Returns the index of the new scan.
{
	int32_t pos = -1;

	int32_t row = (int32_t) data3DHeader.indexBounds.rowMaximum -
		(int32_t) data3DHeader.indexBounds.rowMinimum + 1;
	int32_t col = (int32_t) data3DHeader.indexBounds.columnMaximum -
		(int32_t) data3DHeader.indexBounds.columnMinimum + 1;

	if(data3DHeader.guid.empty())
		return -1;

	StructureNode scan = StructureNode(imf_);
	data3D_.append(scan);
	pos = (int32_t) data3D_.childCount() - 1;

	scan.set("guid", StringNode(imf_, data3DHeader.guid));	//required

	if(!data3DHeader.name.empty())
		scan.set("name", StringNode(imf_, data3DHeader.name));

	if(!data3DHeader.description.empty())
		scan.set("description", StringNode(imf_,
			data3DHeader.description));

	if(data3DHeader.originalGuids.size() > 0 )
	{
		scan.set("originalGuids", VectorNode(imf_));
		VectorNode originalGuids(scan.get("originalGuids"));
		int i;
		for(i = 0; i < (int)data3DHeader.originalGuids.size(); i++)
			originalGuids.append(StringNode(imf_,
				data3DHeader.originalGuids[i]));
	}

// Add various sensor and version strings to scan.
	/// Path names: "/data3D/0/sensorVendor", etc...
	if(!data3DHeader.sensorVendor.empty())
		scan.set("sensorVendor",           StringNode(imf_,
			data3DHeader.sensorVendor));
	if(!data3DHeader.sensorModel.empty())
		scan.set("sensorModel",            StringNode(imf_,
			data3DHeader.sensorModel));
	if(!data3DHeader.sensorSerialNumber.empty())
		scan.set("sensorSerialNumber",     StringNode(imf_,
			data3DHeader.sensorSerialNumber));
	if(!data3DHeader.sensorHardwareVersion.empty())
		scan.set("sensorHardwareVersion",  StringNode(imf_,
			data3DHeader.sensorHardwareVersion));
	if(!data3DHeader.sensorSoftwareVersion.empty())
		scan.set("sensorSoftwareVersion",  StringNode(imf_,
			data3DHeader.sensorSoftwareVersion));
	if(!data3DHeader.sensorFirmwareVersion.empty())
		scan.set("sensorFirmwareVersion",  StringNode(imf_,
			data3DHeader.sensorFirmwareVersion));

// Add temp/humidity to scan.
	/// Path names: "/data3D/0/temperature", etc...
	if(data3DHeader.temperature != E57_FLOAT_MAX)
		scan.set("temperature",      FloatNode(imf_,
			data3DHeader.temperature));

	if(data3DHeader.relativeHumidity != E57_FLOAT_MAX)
		scan.set("relativeHumidity", FloatNode(imf_,
			data3DHeader.relativeHumidity));

	if(data3DHeader.atmosphericPressure != E57_FLOAT_MAX)
		scan.set("atmosphericPressure", FloatNode(imf_,
			data3DHeader.atmosphericPressure));

	if( (data3DHeader.indexBounds.rowMinimum != 0) ||
		(data3DHeader.indexBounds.rowMaximum != 0) ||
		(data3DHeader.indexBounds.columnMinimum != 0) ||
		(data3DHeader.indexBounds.columnMaximum != 0) ||
		(data3DHeader.indexBounds.returnMinimum != 0) ||
		(data3DHeader.indexBounds.returnMaximum != 0) )
		{
		StructureNode ibox = StructureNode(imf_);

//		if( (data3DHeader.indexBounds.rowMinimum != 0) ||
//			(data3DHeader.indexBounds.rowMaximum != 0) )
//		{
			ibox.set("rowMinimum", IntegerNode(imf_,
				data3DHeader.indexBounds.rowMinimum));
			ibox.set("rowMaximum", IntegerNode(imf_,
				data3DHeader.indexBounds.rowMaximum));
//		}
//		if( (data3DHeader.indexBounds.columnMinimum != 0) ||
//			(data3DHeader.indexBounds.columnMaximum != 0) )
//		{
			ibox.set("columnMinimum", IntegerNode(imf_
				, data3DHeader.indexBounds.columnMinimum));
			ibox.set("columnMaximum", IntegerNode(imf_,
				data3DHeader.indexBounds.columnMaximum));
//		}
//		if( (data3DHeader.indexBounds.returnMinimum != 0) ||
//			(data3DHeader.indexBounds.returnMaximum != 0) )
//		{
			ibox.set("returnMinimum", IntegerNode(imf_
				, data3DHeader.indexBounds.returnMinimum));
			ibox.set("returnMaximum", IntegerNode(imf_,
				data3DHeader.indexBounds.returnMaximum));
//		}
		scan.set("indexBounds", ibox);
	}

#ifdef TEST_EXTENSIONS
	StructureNode extbox = StructureNode(imf_);
	extbox.set("ext:rows", IntegerNode(imf_, data3DHeader.indexBounds.rowMaximum + 1));
	extbox.set("ext:columns", IntegerNode(imf_,	data3DHeader.indexBounds.columnMaximum + 1));
	scan.set("ext:indexSize", extbox);
	scan.set("ext:extraStr", StringNode(imf_, "This is another extra string"));
	scan.set("ext:extraFloat", FloatNode(imf_, 3.14159));
#endif

	if( (data3DHeader.intensityLimits.intensityMaximum != 0.) ||
		(data3DHeader.intensityLimits.intensityMinimum != 0.) ){

		StructureNode intbox = StructureNode(imf_);
		if(data3DHeader.pointFields.intensityScaledInteger > 0.)
		{
			double offset = 0.;
			double scale = data3DHeader.pointFields.intensityScaledInteger;

			int64_t rawIntegerMinimum = (int64_t) floor(
				(data3DHeader.intensityLimits.intensityMinimum - offset)/scale +.5);
			int64_t rawIntegerMaximum = (int64_t) floor(
				(data3DHeader.intensityLimits.intensityMaximum - offset)/scale +.5);

			intbox.set("intensityMaximum", ScaledIntegerNode(imf_,
				rawIntegerMaximum, rawIntegerMinimum, rawIntegerMaximum, scale, offset));

			intbox.set("intensityMinimum", ScaledIntegerNode(imf_,
				rawIntegerMinimum, rawIntegerMinimum, rawIntegerMaximum, scale, offset));
		}
		else if(data3DHeader.pointFields.intensityScaledInteger == 0.)
		{
			intbox.set("intensityMaximum", FloatNode(imf_,
				data3DHeader.intensityLimits.intensityMaximum));
			intbox.set("intensityMinimum", FloatNode(imf_,
				data3DHeader.intensityLimits.intensityMinimum));
		}
		else
		{
			intbox.set("intensityMaximum", IntegerNode(imf_,
				(int64_t) data3DHeader.intensityLimits.intensityMaximum));
			intbox.set("intensityMinimum", IntegerNode(imf_,
				(int64_t) data3DHeader.intensityLimits.intensityMinimum));
		}
		scan.set("intensityLimits", intbox);
	}

	if( (data3DHeader.colorLimits.colorRedMaximum != 0.) ||
		(data3DHeader.colorLimits.colorRedMinimum != 0.) ){

		StructureNode colorbox = StructureNode(imf_);
		colorbox.set("colorRedMaximum", IntegerNode(imf_,
			(int64_t) data3DHeader.colorLimits.colorRedMaximum));
		colorbox.set("colorRedMinimum", IntegerNode(imf_,
			(int64_t) data3DHeader.colorLimits.colorRedMinimum));
		colorbox.set("colorGreenMaximum", IntegerNode(imf_,
			(int64_t) data3DHeader.colorLimits.colorGreenMaximum));
		colorbox.set("colorGreenMinimum", IntegerNode(imf_,
			(int64_t) data3DHeader.colorLimits.colorGreenMinimum));
		colorbox.set("colorBlueMaximum", IntegerNode(imf_,
			(int64_t) data3DHeader.colorLimits.colorBlueMaximum));
		colorbox.set("colorBlueMinimum", IntegerNode(imf_,
			(int64_t) data3DHeader.colorLimits.colorBlueMinimum));
		scan.set("colorLimits", colorbox);
	}

// Add Cartesian bounding box to scan.
	/// Path names: "/data3D/0/cartesianBounds/xMinimum", etc...
	if( (data3DHeader.cartesianBounds.xMinimum != -E57_DOUBLE_MAX) ||
		(data3DHeader.cartesianBounds.xMaximum != E57_DOUBLE_MAX) ){

		StructureNode bbox = StructureNode(imf_);
		bbox.set("xMinimum", FloatNode(imf_,
			data3DHeader.cartesianBounds.xMinimum));
		bbox.set("xMaximum", FloatNode(imf_,
			data3DHeader.cartesianBounds.xMaximum));
		bbox.set("yMinimum", FloatNode(imf_,
			data3DHeader.cartesianBounds.yMinimum));
		bbox.set("yMaximum", FloatNode(imf_,
			data3DHeader.cartesianBounds.yMaximum));
		bbox.set("zMinimum", FloatNode(imf_,
			data3DHeader.cartesianBounds.zMinimum));
		bbox.set("zMaximum", FloatNode(imf_,
			data3DHeader.cartesianBounds.zMaximum));
		scan.set("cartesianBounds", bbox);
	}

	if( (data3DHeader.sphericalBounds.rangeMinimum != 0.) ||
		(data3DHeader.sphericalBounds.rangeMaximum != E57_DOUBLE_MAX) ){

		StructureNode sbox = StructureNode(imf_);
		sbox.set("rangeMinimum", FloatNode(imf_,
			data3DHeader.sphericalBounds.rangeMinimum));
		sbox.set("rangeMaximum", FloatNode(imf_,
			data3DHeader.sphericalBounds.rangeMaximum));
		sbox.set("elevationMinimum", FloatNode(imf_,
			data3DHeader.sphericalBounds.elevationMinimum));
		sbox.set("elevationMaximum", FloatNode(imf_,
			data3DHeader.sphericalBounds.elevationMaximum));
		sbox.set("azimuthStart", FloatNode(imf_,
			data3DHeader.sphericalBounds.azimuthStart));
		sbox.set("azimuthEnd", FloatNode(imf_,
			data3DHeader.sphericalBounds.azimuthEnd));
		scan.set("sphericalBounds", sbox);
	}

// Create pose structure for scan.
	/// Path names: "/data3D/0/pose/rotation/w", etc...
	///             "/data3D/0/pose/translation/x", etc...

	if( (data3DHeader.pose.rotation.w != 1.) ||
		(data3DHeader.pose.rotation.x != 0.) ||
		(data3DHeader.pose.rotation.y != 0.) ||
		(data3DHeader.pose.rotation.z != 0.) ||
		(data3DHeader.pose.translation.x != 0.) ||
		(data3DHeader.pose.translation.y != 0.) ||
		(data3DHeader.pose.translation.z != 0.) )
	{
		StructureNode pose = StructureNode(imf_);
		scan.set("pose", pose);

		if( (data3DHeader.pose.rotation.w != 1.) ||
			(data3DHeader.pose.rotation.x != 0.) ||
			(data3DHeader.pose.rotation.y != 0.) ||
			(data3DHeader.pose.rotation.z != 0.) )
		{
			StructureNode rotation = StructureNode(imf_);
			rotation.set("w", FloatNode(imf_, data3DHeader.pose.rotation.w));
			rotation.set("x", FloatNode(imf_, data3DHeader.pose.rotation.x));
			rotation.set("y", FloatNode(imf_, data3DHeader.pose.rotation.y));
			rotation.set("z", FloatNode(imf_, data3DHeader.pose.rotation.z));
			pose.set("rotation", rotation);
		}

		if( (data3DHeader.pose.translation.x != 0.) ||
			(data3DHeader.pose.translation.y != 0.) ||
			(data3DHeader.pose.translation.z != 0.) )
		{
			StructureNode translation = StructureNode(imf_);
			translation.set("x", FloatNode(imf_, data3DHeader.pose.translation.x));
			translation.set("y", FloatNode(imf_, data3DHeader.pose.translation.y));
			translation.set("z", FloatNode(imf_, data3DHeader.pose.translation.z));
			pose.set("translation", translation);
		}
	}
// Add start/stop acquisition times to scan.
	/// Path names: "/data3D/0/acquisitionStart/dateTimeValue",
	///             "/data3D/0/acquisitionEnd/dateTimeValue"
	if(data3DHeader.acquisitionStart.dateTimeValue > 0.) {
		StructureNode acquisitionStart = StructureNode(imf_);
		scan.set("acquisitionStart", acquisitionStart);
		acquisitionStart.set("dateTimeValue",
			FloatNode(imf_, data3DHeader.acquisitionStart.dateTimeValue));
		acquisitionStart.set("isAtomicClockReferenced",
			IntegerNode(imf_, data3DHeader.acquisitionStart.isAtomicClockReferenced));
	}
	if(data3DHeader.acquisitionEnd.dateTimeValue > 0.) {
		StructureNode acquisitionEnd = StructureNode(imf_);
		scan.set("acquisitionEnd", acquisitionEnd);
		acquisitionEnd.set("dateTimeValue",
			FloatNode(imf_, data3DHeader.acquisitionEnd.dateTimeValue));
		acquisitionEnd.set("isAtomicClockReferenced",
			IntegerNode(imf_, data3DHeader.acquisitionEnd.isAtomicClockReferenced));
	}

// Add grouping scheme area
    /// Path name: "/data3D/0/pointGroupingSchemes"
	if(!data3DHeader.pointGroupingSchemes.groupingByLine.idElementName.empty())
	{
		StructureNode pointGroupingSchemes = StructureNode(imf_);
		scan.set("pointGroupingSchemes", pointGroupingSchemes);

    /// Add a line grouping scheme
    /// Path name: "/data3D/0/pointGroupingSchemes/groupingByLine"
		StructureNode groupingByLine = StructureNode(imf_);
		pointGroupingSchemes.set("groupingByLine", groupingByLine);

   ///	data3DHeader.pointGroupingSchemes.groupingByLine.idElementName));
		bool byColumn = true;	//default should be "columnIndex"
		if(data3DHeader.pointGroupingSchemes.groupingByLine.idElementName.compare("rowIndex") == 0)
			byColumn = false;

    /// Add idElementName to groupingByLine, specify a line is column or row oriented
    /// Path name: "/data3D/0/pointGroupingSchemes/groupingByLine/idElementName"
		if(byColumn)
			groupingByLine.set("idElementName", StringNode(imf_,"columnIndex"));
		else
			groupingByLine.set("idElementName", StringNode(imf_,"rowIndex"));
 
// Make a prototype of datatypes that will be stored in LineGroupRecord.
    /// This prototype will be used in creating the groups CompressedVector.
    /// Will define path names like:
    ///     "/data3D/0/pointGroupingSchemes/groupingByLine/groups/0/idElementValue"

		int64_t groupsSize = data3DHeader.pointGroupingSchemes.groupingByLine.groupsSize;
		int64_t countSize = data3DHeader.pointGroupingSchemes.groupingByLine.pointCountSize;
		int64_t pointsSize = data3DHeader.pointsSize;

		StructureNode lineGroupProto = StructureNode(imf_);
		lineGroupProto.set("startPointIndex",   IntegerNode(imf_, 0, 0, pointsSize - 1));
		lineGroupProto.set("idElementValue",    IntegerNode(imf_, 0, 0, groupsSize - 1));
		lineGroupProto.set("pointCount",        IntegerNode(imf_, 0, 0, countSize));

		//Not supported in this Simple API for now
/*
		StructureNode bbox = StructureNode(imf_);
		bbox.set("xMinimum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,	data3DHeader.pointFields.pointRangeMaximum));
		bbox.set("xMaximum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,	data3DHeader.pointFields.pointRangeMaximum));
		bbox.set("yMinimum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,	data3DHeader.pointFields.pointRangeMaximum));
		bbox.set("yMaximum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,	data3DHeader.pointFields.pointRangeMaximum));
		bbox.set("zMinimum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,	data3DHeader.pointFields.pointRangeMaximum));
		bbox.set("zMaximum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,	data3DHeader.pointFields.pointRangeMaximum));
		lineGroupProto.set("cartesianBounds", bbox);

		StructureNode sbox = StructureNode(imf_);
		sbox.set("rangeMinimum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,	data3DHeader.pointFields.pointRangeMaximum));
		sbox.set("rangeMaximum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,	data3DHeader.pointFields.pointRangeMaximum));
		sbox.set("elevationMinimum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.angleMinimum, data3DHeader.pointFields.angleMaximum));
		sbox.set("elevationMaximum", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.angleMinimum, data3DHeader.pointFields.angleMaximum));
		sbox.set("azimuthStart", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.angleMinimum, data3DHeader.pointFields.angleMaximum));
		sbox.set("azimuthEnd", FloatNode(imf_, 0., E57_SINGLE,
				data3DHeader.pointFields.angleMinimum, data3DHeader.pointFields.angleMaximum));
		lineGroupProto.set("sphericalBounds", sbox);
*/
    /// Make empty codecs vector for use in creating groups CompressedVector.
    /// If this vector is empty, it is assumed that all fields will use the BitPack codec.
		VectorNode lineGroupCodecs = VectorNode(imf_, true);

    /// Create CompressedVector for storing groups.  
    /// Path Name: "/data3D/0/pointGroupingSchemes/groupingByLine/groups".
    /// We use the prototype and empty codecs tree from above.
    /// The CompressedVector will be filled by code below.
		CompressedVectorNode groups = CompressedVectorNode(imf_, lineGroupProto, lineGroupCodecs);
		groupingByLine.set("groups", groups);
	}

// Make a prototype of datatypes that will be stored in points record.
    /// This prototype will be used in creating the points CompressedVector.
    /// Using this proto in a CompressedVector will define path names like:
    ///      "/data3D/0/points/0/cartesianX"
    StructureNode proto = StructureNode(imf_);

// Because ScaledInteger min/max are the raw integer min/max, we must calculate them from the data min/max
	double pointRangeScale = data3DHeader.pointFields.pointRangeScaledInteger;
	double pointRangeOffset = 0.;
	int64_t pointRangeMinimum = (int64_t) floor((data3DHeader.pointFields.pointRangeMinimum - pointRangeOffset)/pointRangeScale +.5);
	int64_t pointRangeMaximum = (int64_t) floor((data3DHeader.pointFields.pointRangeMaximum - pointRangeOffset)/pointRangeScale +.5);

	if(data3DHeader.pointFields.cartesianXField){
		if(data3DHeader.pointFields.pointRangeScaledInteger > 0.)
			proto.set("cartesianX",  ScaledIntegerNode(imf_, 0,
				pointRangeMinimum, pointRangeMaximum, pointRangeScale, pointRangeOffset));
		else
			proto.set("cartesianX",  FloatNode(imf_, 0.,
				(data3DHeader.pointFields.pointRangeScaledInteger < 0.) ? E57_DOUBLE : E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,
				data3DHeader.pointFields.pointRangeMaximum));
	}
	if(data3DHeader.pointFields.cartesianYField){
		if(data3DHeader.pointFields.pointRangeScaledInteger > 0.)
			proto.set("cartesianY",  ScaledIntegerNode(imf_, 0,
				pointRangeMinimum, pointRangeMaximum, pointRangeScale, pointRangeOffset));
		else
			proto.set("cartesianY",  FloatNode(imf_, 0.,
				(data3DHeader.pointFields.pointRangeScaledInteger < 0.) ? E57_DOUBLE : E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,
				data3DHeader.pointFields.pointRangeMaximum));
	}
#ifdef TEST_EXTENSIONS
	proto.set("ext:extraField1", IntegerNode(imf_, 0, 0, 255));
#endif
	if(data3DHeader.pointFields.cartesianZField){
		if(data3DHeader.pointFields.pointRangeScaledInteger > 0.)
			proto.set("cartesianZ",  ScaledIntegerNode(imf_, 0,
				pointRangeMinimum, pointRangeMaximum, pointRangeScale, pointRangeOffset));
		else
			proto.set("cartesianZ",  FloatNode(imf_, 0.,
				(data3DHeader.pointFields.pointRangeScaledInteger < 0.) ? E57_DOUBLE : E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,
				data3DHeader.pointFields.pointRangeMaximum));
	}

	if(data3DHeader.pointFields.sphericalRangeField){
		if(data3DHeader.pointFields.pointRangeScaledInteger > 0.)
			proto.set("sphericalRange",  ScaledIntegerNode(imf_, 0,
				pointRangeMinimum, pointRangeMaximum, pointRangeScale, pointRangeOffset));
		else
			proto.set("sphericalRange",  FloatNode(imf_, 0.,
				(data3DHeader.pointFields.pointRangeScaledInteger < 0.) ? E57_DOUBLE : E57_SINGLE,
				data3DHeader.pointFields.pointRangeMinimum,
				data3DHeader.pointFields.pointRangeMaximum));
	    }

	double angleScale = data3DHeader.pointFields.angleScaledInteger;
	double angleOffset = 0.;
	int64_t angleMinimum = (int64_t) floor((data3DHeader.pointFields.angleMinimum - angleOffset)/angleScale +.5);
	int64_t angleMaximum = (int64_t) floor((data3DHeader.pointFields.angleMaximum - angleOffset)/angleScale +.5);

	if(data3DHeader.pointFields.sphericalAzimuthField){
		if(data3DHeader.pointFields.angleScaledInteger > 0.)
			proto.set("sphericalAzimuth",  ScaledIntegerNode(imf_, 0,
				angleMinimum, angleMaximum, angleScale, angleOffset));
		else
			proto.set("sphericalAzimuth",  FloatNode(imf_, 0.,
				(data3DHeader.pointFields.angleScaledInteger < 0.) ? E57_DOUBLE : E57_SINGLE,
				data3DHeader.pointFields.angleMinimum,
				data3DHeader.pointFields.angleMaximum));
	}

	if(data3DHeader.pointFields.sphericalElevationField){
		if(data3DHeader.pointFields.angleScaledInteger > 0.)
			proto.set("sphericalElevation",  ScaledIntegerNode(imf_, 0,
				angleMinimum, angleMaximum, angleScale, angleOffset));
		else
			proto.set("sphericalElevation",  FloatNode(imf_, 0.,
				(data3DHeader.pointFields.angleScaledInteger < 0.) ? E57_DOUBLE : E57_SINGLE,
				data3DHeader.pointFields.angleMinimum,
				data3DHeader.pointFields.angleMaximum));
	}

#ifdef TEST_EXTENSIONS
	proto.set("ext:extraField2", IntegerNode(imf_, 0, 0, 255));
#endif

	if(data3DHeader.pointFields.intensityField){
		if(data3DHeader.pointFields.intensityScaledInteger > 0.)
		{
			double offset = 0; //could be data3DHeader.intensityLimits.intensityMinimum;
			double scale = data3DHeader.pointFields.intensityScaledInteger;
			int64_t rawIntegerMinimum = (int64_t) floor((data3DHeader.intensityLimits.intensityMinimum - offset)/scale +.5);
			int64_t rawIntegerMaximum = (int64_t) floor((data3DHeader.intensityLimits.intensityMaximum - offset)/scale +.5);
			proto.set("intensity",  ScaledIntegerNode(imf_, 0,
				rawIntegerMinimum, rawIntegerMaximum, scale, offset));
		}
		else if(data3DHeader.pointFields.intensityScaledInteger == 0.)
			proto.set("intensity",  FloatNode(imf_, 0.,	E57_SINGLE,
				data3DHeader.intensityLimits.intensityMinimum,
				data3DHeader.intensityLimits.intensityMaximum));
		else
			proto.set("intensity",  IntegerNode(imf_, 0,
				(int64_t) data3DHeader.intensityLimits.intensityMinimum,
				(int64_t) data3DHeader.intensityLimits.intensityMaximum));
	}

	if(data3DHeader.pointFields.colorRedField)
		proto.set("colorRed",    IntegerNode(imf_, 0,
			(int64_t) data3DHeader.colorLimits.colorRedMinimum,
			(int64_t) data3DHeader.colorLimits.colorRedMaximum));
	if(data3DHeader.pointFields.colorGreenField)
		proto.set("colorGreen",  IntegerNode(imf_, 0,
			(int64_t) data3DHeader.colorLimits.colorGreenMinimum,
			(int64_t) data3DHeader.colorLimits.colorGreenMaximum));
	if(data3DHeader.pointFields.colorBlueField)
		proto.set("colorBlue",   IntegerNode(imf_, 0,
			(int64_t) data3DHeader.colorLimits.colorBlueMinimum,
			(int64_t) data3DHeader.colorLimits.colorBlueMaximum));

	if(data3DHeader.pointFields.returnIndexField)
		proto.set("returnIndex", IntegerNode(imf_, 0, E57_UINT8_MIN,
			data3DHeader.pointFields.returnMaximum));
    if(data3DHeader.pointFields.returnCountField)
		proto.set("returnCount", IntegerNode(imf_, 0, E57_UINT8_MIN,
			data3DHeader.pointFields.returnMaximum));

	if(data3DHeader.pointFields.rowIndexField)
		proto.set("rowIndex",    IntegerNode(imf_, 0, E57_UINT32_MIN,
			data3DHeader.pointFields.rowIndexMaximum));
	if(data3DHeader.pointFields.columnIndexField)
		proto.set("columnIndex", IntegerNode(imf_, 0, E57_UINT32_MIN,
			data3DHeader.pointFields.columnIndexMaximum));

	if(data3DHeader.pointFields.timeStampField){
		if(data3DHeader.pointFields.timeMaximum == E57_FLOAT_MAX)
			proto.set("timeStamp",  FloatNode(imf_, 0., E57_SINGLE, E57_FLOAT_MIN, E57_FLOAT_MAX));
		else if(data3DHeader.pointFields.timeMaximum == E57_DOUBLE_MAX)
			proto.set("timeStamp",  FloatNode(imf_, 0., E57_DOUBLE, E57_DOUBLE_MIN, E57_DOUBLE_MAX));
		else
			proto.set("timeStamp",  IntegerNode(imf_, 0,
				(int64_t) -data3DHeader.pointFields.timeMaximum,
				(int64_t) data3DHeader.pointFields.timeMaximum));
	}
#ifdef TEST_EXTENSIONS
	proto.set("ext:extraField3", IntegerNode(imf_, 0, 0, 255));
#endif
	if(data3DHeader.pointFields.cartesianInvalidStateField)
		proto.set("cartesianInvalidState",  IntegerNode(imf_, 0, 0, 2));
	if(data3DHeader.pointFields.sphericalInvalidStateField)
		proto.set("sphericalInvalidState", IntegerNode(imf_, 0, 0, 2));
	if(data3DHeader.pointFields.isIntensityInvalidField)
		proto.set("isIntensityInvalid", IntegerNode(imf_, 0, 0, 1));
	if(data3DHeader.pointFields.isColorInvalidField)
		proto.set("isColorInvalid", IntegerNode(imf_, 0, 0, 1));
	if(data3DHeader.pointFields.isTimeStampInvalidField)
		proto.set("isTimeStampInvalid", IntegerNode(imf_, 0, 0, 1));

//   proto.set("demo:extra2", StringNode(imf_));	//Extension here

// Make empty codecs vector for use in creating points CompressedVector.
    /// If this vector is empty, it is assumed that all fields will use the BitPack codec.
    VectorNode codecs = VectorNode(imf_, true);

// Create CompressedVector for storing points.  Path Name: "/data3D/0/points".
    /// We use the prototype and empty codecs tree from above.
    /// The CompressedVector will be filled by code below.
    CompressedVectorNode points = CompressedVectorNode(imf_, proto, codecs);
    scan.set("points", points);
	return pos;
};

CompressedVectorWriter	WriterImpl :: SetUpData3DPointsData(
	int32_t		dataIndex,
	int64_t		count,
	double*		cartesianX,			//!< pointer to a buffer with the X coordinate (in meters) of the point in Cartesian coordinates
	double*		cartesianY,			//!< pointer to a buffer with the Y coordinate (in meters) of the point in Cartesian coordinates
	double*		cartesianZ,			//!< pointer to a buffer with the Z coordinate (in meters) of the point in Cartesian coordinates
	int8_t*		cartesianInvalidState,	//!< Value = 0 if the point is considered valid, 1 otherwise

	double*		intensity,			//!< pointer to a buffer with the Point response intensity. Unit is unspecified
	int8_t*		isIntensityInvalid,	//!< Value = 0 if the intensity is considered valid, 1 otherwise

	uint16_t*	colorRed,			//!< pointer to a buffer with the Red color coefficient. Unit is unspecified
	uint16_t*	colorGreen,			//!< pointer to a buffer with the Green color coefficient. Unit is unspecified
	uint16_t*	colorBlue,			//!< pointer to a buffer with the Blue color coefficient. Unit is unspecified
	int8_t*		isColorInvalid,		//!< Value = 0 if the color is considered valid, 1 otherwise

	double*		sphericalRange,		//!< pointer to a buffer with the range (in meters) of points in spherical coordinates. Shall be non-negative
	double*		sphericalAzimuth,	//!< pointer to a buffer with the Azimuth angle (in radians) of point in spherical coordinates
	double*		sphericalElevation,	//!< pointer to a buffer with the Elevation angle (in radians) of point in spherical coordinates
	int8_t*		sphericalInvalidState, //!< Value = 0 if the range is considered valid, 1 otherwise

	int32_t*	rowIndex,			//!< pointer to a buffer with the row number of point (zero based). This is useful for data that is stored in a regular grid.Shall be in the interval (0, 2^63).
	int32_t*	columnIndex,		//!< pointer to a buffer with the column number of point (zero based). This is useful for data that is stored in a regular grid. Shall be in the interval (0, 2^63).
	int8_t*		returnIndex,		//!< pointer to a buffer with the number of this return (zero based). That is, 0 is the first return, 1 is the second, and so on. Shall be in the interval (0, returnCount). Only for multi-return sensors. 
	int8_t*		returnCount,		//!< pointer to a buffer with the total number of returns for the pulse that this corresponds to. Shall be in the interval (0, 2^63). Only for multi-return sensors. 

	double*		timeStamp,			//!< pointer to a buffer with the time (in seconds) since the start time for the data, which is given by acquisitionStart in the parent Data3D Structure. Shall be non-negative
	int8_t*		isTimeStampInvalid	//!< Value = 0 if the timeStamp is considered valid, 1 otherwise

	)
{
#ifdef TEST_EXTENSIONS
	uint8_t*		extraField1 = new uint8_t[(unsigned) count];
	uint8_t*		extraField2 = new uint8_t[(unsigned) count];
	uint8_t*		extraField3 = new uint8_t[(unsigned) count];

	for( int i = 0; i < count; i++) {
		extraField1[i] = i % 256;
		extraField2[i] = i % 256;
		extraField3[i] = i % 256;
	}
#endif
	StructureNode scan(data3D_.get(dataIndex));
	CompressedVectorNode points(scan.get("points"));
	StructureNode proto(points.prototype());

	vector<SourceDestBuffer> sourceBuffers;
	if(proto.isDefined("cartesianX") && (cartesianX != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "cartesianX",  cartesianX,  (unsigned) count, true, true));
	if(proto.isDefined("cartesianY") && (cartesianY != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "cartesianY",  cartesianY,  (unsigned) count, true, true));
#ifdef TEST_EXTENSIONS
	if(proto.isDefined("ext:extraField1"))
		sourceBuffers.push_back(SourceDestBuffer(imf_,"ext:extraField1", extraField1, (unsigned) count, true));
#endif
	if(proto.isDefined("cartesianZ") && (cartesianZ != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "cartesianZ",  cartesianZ,  (unsigned) count, true, true));

	if(proto.isDefined("sphericalRange") && (sphericalRange != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "sphericalRange",  sphericalRange,  (unsigned) count, true, true));
	if(proto.isDefined("sphericalAzimuth") && (sphericalAzimuth != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "sphericalAzimuth",  sphericalAzimuth,  (unsigned) count, true, true));
	if(proto.isDefined("sphericalElevation") && (sphericalElevation != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "sphericalElevation",  sphericalElevation,  (unsigned) count, true, true));

#ifdef TEST_EXTENSIONS
	if(proto.isDefined("ext:extraField2"))
		sourceBuffers.push_back(SourceDestBuffer(imf_,"ext:extraField2", extraField2, (unsigned) count, true));
#endif

	if(proto.isDefined("intensity") && (intensity != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "intensity",   intensity,   (unsigned) count, true, true));

	if(proto.isDefined("colorRed") && (colorRed != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "colorRed",    colorRed,    (unsigned) count, true));
	if(proto.isDefined("colorGreen") && (colorGreen != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "colorGreen",  colorGreen,  (unsigned) count, true));
	if(proto.isDefined("colorBlue") && (colorBlue != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "colorBlue",   colorBlue,   (unsigned) count, true));

	if(proto.isDefined("returnIndex") && (returnIndex != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "returnIndex", returnIndex, (unsigned) count, true));
	if(proto.isDefined("returnCount") && (returnCount != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "returnCount", returnCount, (unsigned) count, true));

	if(proto.isDefined("rowIndex") && (rowIndex != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "rowIndex",    rowIndex,    (unsigned) count, true));
	if(proto.isDefined("columnIndex") && (columnIndex != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "columnIndex", columnIndex, (unsigned) count, true));

	if(proto.isDefined("timeStamp") && (timeStamp != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "timeStamp",   timeStamp,   (unsigned) count, true));

#ifdef TEST_EXTENSIONS
	if(proto.isDefined("ext:extraField3"))
		sourceBuffers.push_back(SourceDestBuffer(imf_,"ext:extraField3", extraField3, (unsigned) count, true));
#endif
	if(proto.isDefined("cartesianInvalidState") && (cartesianInvalidState != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "cartesianInvalidState",       cartesianInvalidState,       (unsigned) count, true));
	if(proto.isDefined("sphericalInvalidState") && (sphericalInvalidState != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "sphericalInvalidState",       sphericalInvalidState,       (unsigned) count, true));
	if(proto.isDefined("isIntensityInvalid") && (isIntensityInvalid != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "isIntensityInvalid",       isIntensityInvalid,       (unsigned) count, true));
	if(proto.isDefined("isColorInvalid") && (isColorInvalid != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "isColorInvalid",       isColorInvalid,       (unsigned) count, true));
	if(proto.isDefined("isTimeStampInvalid") && (isTimeStampInvalid != NULL))
		sourceBuffers.push_back(SourceDestBuffer(imf_, "isTimeStampInvalid",       isTimeStampInvalid,       (unsigned) count, true));

	CompressedVectorWriter writer = points.writer(sourceBuffers);

	return writer;
};
//! This funtion writes out the group data
bool	WriterImpl :: WriteData3DGroupsData(
						int32_t		dataIndex,			//!< data block index given by the NewData3D
						int32_t		groupCount,			//!< size of each of the buffers given
						int64_t*	idElementValue,		//!< index for this group
						int64_t*	startPointIndex,	//!< Starting index in to the "points" data vector for the groups
						int64_t*	pointCount			//!< size of each of the groups given
						)								//!< \return Return true if sucessful, false otherwise
{

	if( (dataIndex < 0) || (dataIndex >= data3D_.childCount()))
		return false;

	StructureNode scan(data3D_.get(dataIndex));

	if(!scan.isDefined("pointGroupingSchemes"))
		return false;
	StructureNode pointGroupingSchemes(scan.get("pointGroupingSchemes"));

	if(!pointGroupingSchemes.isDefined("groupingByLine"))
		return false;
	StructureNode groupingByLine(pointGroupingSchemes.get("groupingByLine"));

	if(!groupingByLine.isDefined("groups"))
		return false;
	CompressedVectorNode groups(groupingByLine.get("groups"));

	vector<SourceDestBuffer> groupSDBuffers;
    groupSDBuffers.push_back(SourceDestBuffer(imf_, "idElementValue",  idElementValue,   (unsigned) groupCount, true));
    groupSDBuffers.push_back(SourceDestBuffer(imf_, "startPointIndex", startPointIndex,  (unsigned) groupCount, true));
    groupSDBuffers.push_back(SourceDestBuffer(imf_, "pointCount",      pointCount,       (unsigned) groupCount, true));

	CompressedVectorWriter writer = groups.writer(groupSDBuffers);
    writer.write(groupCount);
    writer.close();

	return true;
};




